当前位置:   article > 正文

Neo4j 4.x 社区版数据导入及Spring-Data-Neo4j 5.x、6.x使用案例_spring-data-neo4j:6.x

spring-data-neo4j:6.x

背景及版本介绍

最近看因为工作原因开始接触Neo4j,发现Neo4j本身资料就比较少,而且版本差异比较大,比较流行的书籍《Neo4j权威指南》用是的3.x 社区版(使用Java8),而我由于本机装的是java11,一开始使用的就是社区版 4.3.6,因此很多3.x的命令是无法使用,但也不值当重新装一个3.x的环境(这点不得不夸一下Neo4j无论是安装启动,neo4j在windows上体验都和Linux非常相似,自己学习不用重新搞个Linux环境,非常方便,其实重装一次也不麻烦,只是比较懒)。

在碰到问题时还是尝试搜索资料,解决了一下相关的问题,其实很多问题官方文档已经给出了答案,只是官方文档太繁琐,很有可能一开始看忽略掉问题。

我这边主要碰到的问题是数据导入命令的变化,还是社区版的切换库问题。因此主要说的也是这两个问题。注意本篇博客并不教搭建和CQL语法,因为这些内容是比较通用的,网上3.x的教程也完全可以用。

然后就是Spring-Data-Neo4j操作Neo4j,我看的教程使用SpringBoot版本是2.3.5,配套的Spring-Data-Neo4j是5.3.5,然而我用最新IDEA创建的SpringBoot项目是2.5.5,配套的Spring-Data-Neo4j是6.1.5。

其实Spring中很多starter升级版本对API的影响不大,或者基本是兼容的,但Spring-Data-Neo4j 的 API 变了多了,因此两个版本我都去简单实现了一下。也让初入坑的小伙伴了解一下,不要像我一样无所适从。

Neo4j 4.3.6数据导入

Neo4j本质上是NoSQL,因此开发对Neo4j的学习可能主要集中在CQL上。但我觉得图数据库造数据要比RDB麻烦的多,RDB无非就插入几条数据而已,而且有图形化界面,操作起来很麻烦。

图数据库要造节点,造关系,自带的web图形界面可以查询,插入操作还是要使用CQL语句,一开始对CQL语句操作不熟悉经常把关系搞得很乱,因此我决定导入现成的关系,学起来也比较有意思,有成就感。

想导入数据首先要有数据,网上找了一下没有现成的CSV,那就只能自己做了,我从网上找了一个有意思的,是火影忍者的关系图,内容如下

[
    {
        "entity_1": "千手纲手",
        "entity_2": "加藤断",
        "relationship": "女友"
    },
    {
        "entity_1": "加藤断",
        "entity_2": "千手纲手",
        "relationship": "男友"
    },
    {
        "entity_1": "加藤断",
        "entity_2": "静音",
        "relationship": "叔叔"
    },
    //...
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

这种是不能直接导入的,需要改为neo4j可用的csv格式,因此我写了一份代码把json转为两份CSV,一份是人物,一个是关系,具体数据如下

# 角色csv
roleId:ID,name,:LABEL
role_0,千手纲手,naruto
role_1,加藤断,naruto
role_2,静音,naruto
...
# 关系csv
:START_ID,:END_ID,:TYPE
role_0,role_1,女友
role_1,role_0,男友
role_1,role_2,叔叔
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

后面我会把csv传上来,简单解释一下,角色csv是提供节点的,节点中有两个属性,roleId和name,节点的标签均为naruto,这里roleId并不是数据库中的id,它只是作为关系节点设置关系的依据,也就是在关系CSV中,START_ID和END_ID中的数据,TYPE字段这样写就相当于不同的关系都设置了一种类型,其实更通用的写法可以设置成这样

# 关系csv
:START_ID,:END_ID,relation,:TYPE
role_0,role_1,女友,narutoRelation
role_1,role_0,男友,narutoRelation
role_1,role_2,叔叔,narutoRelation
  • 1
  • 2
  • 3
  • 4
  • 5

也就是类型都是 narutoRelation,具体关系在边的relation属性里,有兴趣的可以自己修改一下导入。

我这里使用的是neo4j import方式导入数据,注意这种方式只能在第一次初始化数据的时候才能使用,也就是这个库必须是无数据的新库,但很有你的Neo4j库是有数据的,因此我们不会传到默认的neo4j库,具体操作下面还会有详细说明。还有一点需要提前说明,Neo4j 3.x 是使用bin目录下的neo4j-import命令,但4.x是没有这个命令的, 因为这个命令被合并到neo4j-admin命令里了。

具体操作是先把两个CSV,放到import文件夹下,一定要放到import文件夹下,然后跳到neo4j的bin目录下,使用GitBash(CMD也可以)输入如下命令

./neo4j-admin.bat import 
--database=naruto.db 
--nodes=import/role.csv 
--relationships=import/relation.csv
  • 1
  • 2
  • 3
  • 4

用我这个命令会产生一个naruto.db的库,需要修改配置文件并重启,原因是我的默认库neo4j已经有数据量,而有数据的库是不能用import命令导入的,后文注意第5点有详细说明。

具体的操作是找到conf/neo4j.conf,修改配置文件中的库

# The name of the default database
#dbms.default_database=neo4j
dbms.default_database=naruto.db
  • 1
  • 2
  • 3

最后使用命令重启

neo4j restart
  • 1

导入数据后部分图(MATCH (n:naruto) RETURN n LIMIT 25)
火影人物关系图(局部)
需要注意以下几点

  1. 在windows环境下,在CMD输入命令可以直接用命令,但在GitBash中,需要使用neo4j-admin.bat。
  2. –nodes,–relationships后面是等号(=),3.x是冒号(:)
  3. –nodes,–relationships后面的路径,并不是你现在所在目录的相对路径,而是以neo4j的根目录作为起始的相对目录,真正执行的时候会拼接上neo4j所在的目录,因此我建议使用时就使用import开头。
  4. import 命令必须导入到一个空库,如果你的库已经有数据,是无法导入成功的,这也是我在–database=后面加了一个新数据库的原因。
  5. neo4j 3.x的默认数据库叫graph.db,而4.x默认数据库为neo4j,我这样写直接会新建一个名为naruto.db的数据库,但是社区版不支持创建新库,因此需要找到conf/neo4j.conf,修改配置文件中的库,并重启,此时旧库neo4j虽然在web页面可见,但却不能使用,切换到新的naruto.db就可以看到数据了。
    切换数据库

更多关于Neo4j的操作可以看一下官方文档

SDN使用案例

两个案例我会挂到gitee上,因此就不描述目录结构,引入包类等信息了,简单写一下思路和异同点。

SDN 5.3.5 使用案例

新建一个SpringBoot项目,我使用的版本是2.3.5.RELEASE,因为Spring-Data-Neo4j是属于SpringData项目的,因此填写依赖的时候不需要填写它的版本,会根据SpringBoot版本自动设置。

添加依赖

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-neo4j</artifactId>
 </dependency>
  • 1
  • 2
  • 3
  • 4

添加以后,会可以从maven依赖中查看版本。
在这里插入图片描述

配置文件

不同版本推荐写法不同,5.3.5的properties推荐写法

spring.data.neo4j.uri=bolt://localhost:7687
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=123456
  • 1
  • 2
  • 3

Entity

创建两个entity类,分别代表节点和边关系。

节点类

对应的标签是naruto。

@Data
@NodeEntity(label = "naruto")
public class NarutoRole implements Serializable {
    @Id
    @GeneratedValue
    private Long id;
    @Property
    private String name;
    @Property
    private String roleId;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

注意这里标记节点类是使用@NodeEntity,标记属性是@Property,@Id代表这个属性是索引Id,@GeneratedValue是自动生成,文档说可以自定义生成策略,我没实验。

关系类
@Data
@RelationshipEntity(type = "narutoRelation")
public class NarutoRelation {
    @Id
    @GeneratedValue
    private Long id;

    @StartNode
    private NarutoRole fromNode;

    @EndNode
    private NarutoRole toNode;

    @Property
    private String relation;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

注意这里的关系类(class)的类型(type)都是narutoRelation,如果想要一种把“师徒”,“对手”这些关系定义为不同的类型(type),需要定义不同的类(类)。

使用@RelationshipEntity代表这是一个边关系类。这里需要两个比较中要的注解@StartNode和@EndNode,这个边关系的起始都是NarutoRole类,如果是不同的类型可以根据情况修改。

Repository

Repository简单的多,创建相应的接口继承了Neo4jRepository就可以直接继承相应的增删改查方法,只要注意两个泛型,第一个代表增删改查对应的Entity,第二个代表Entity的id,neo4j自动生成的id都是Long类型,我也建议大家使用Long类型,后面有一次使用了字符串作为Id,自带的findById()方法就不能用了。

@Repository
public interface RelationshipRepository extends Neo4jRepository<NarutoRelation,Long> {}
  • 1
  • 2

当然可以也可以执行CQL语句,在这个版本下

@Repository
public interface RelationshipRepository extends Neo4jRepository<NarutoRelation,Long> {

    @Query("MATCH (m:naruto {name::#{#from}}),(n:naruto {name::#{#to}})" +
            "CREATE (m)-[:narutoRelation{relation::#{#relation}}]->(n)")
    void createRelation(@Param("from")String from, @Param("relation")String relation, @Param("to")String to);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这里也简单说一个知识点,我现在用的版本的Spring Data在注入参数时,需要用 :#{#paramName} 这个整体来注入,类似MyBatis的${paramName},其他数据库JPA的使用方式应该也是一样的,其实我看的视频教程里可以用{0},{1}这种方式来注入参数,但我这边实践没有生效,也没仔细研究,就直接用这种了,实际开发用这种的可能性也更大,可读性更强一点。

测试类

@SpringBootTest
class SpringDemoTestApplicationTests {

    @Test
    void contextLoads() {
    }

    @Autowired
    RoleRepository roleRepository;

    @Autowired
    RelationshipRepository relationshipRepository;

    @Test
    public void testBaseRepositoryMethod(){

        // 查找节点
        Optional<NarutoRole> byId = roleRepository.findById(46L);
        byId.orElse(null);

        // 创建节点
        NarutoRole WhiteJue = new NarutoRole();
        WhiteJue.setName("白绝");
        WhiteJue.setRoleId("role_70");
        roleRepository.save(WhiteJue);
        // 创建边关系
        Optional<NarutoRole> byId = roleRepository.findById(22L);

        NarutoRole ban = byId.get();

        byId = roleRepository.findById(63L);

        NarutoRole whitJue = byId.get();

        NarutoRelation relation = new NarutoRelation();

        relation.setFromNode(ban);
        relation.setToNode(whitJue);
        relation.setRelation("制造");
        relationshipRepository.save(relation);
    }

    @Test
    public void testCypherQL(){
        relationshipRepository.createRelation("黑绝","寄生","白绝");
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

SDN 6.1.5 使用案例

案例介绍分析

6.0的案例没有使用之前导入的数据,因为人物关系已经比较全了,因此我准备重新添加两个节点以及他们之间的关系,含义是竹内顺子给漩涡鸣人配音。

按照我们预想的场景,节点关系应该是:竹内顺子-[配音]->漩涡鸣人,请记住这个关系,配音是从竹内顺子指向漩涡鸣人。

添加依赖和上文相同,只是SpringBoot版本变化为2.5.5,不做过多说明。

配置文件

6.1.5推荐写法

spring.data.neo4j.uri=bolt://localhost:7687
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=123456
  • 1
  • 2
  • 3

Entity

节点类

火影角色entity

@Node("Naruto")
@Data
public class NarutoRole {

    @Id
    @GeneratedValue
    private Long id;

    @Property
    private String name;

    @Property
    private String organization;

    @Relationship(type = "DUB_TO", direction = Relationship.Direction.INCOMING)
    private List<DubTo> dubTo;

    @Relationship(type = "DUBBER", direction = Relationship.Direction.INCOMING)
    private Dubber dubber;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

配音演员Entity

@Node("NarutoDubber")
@Data
public class Dubber {
    @Id
    private final String name;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以看到这里已经没有@NodeEntity,不是不推荐用,是根本没有这个注解。而且还添加了两个属性作为节点和关系。

然后是关系Entity

@RelationshipProperties
@Data
@Builder
public class DubTo {

    @Id
    @GeneratedValue
    private Long id;

    @TargetNode
    private final Dubber dubber;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这里可以看到用的是注解是@RelationshipProperties,特别要注意属性里有个@TargetNode,翻译过来是目标节点,我在上面特意说了从我的理解上是从配音演员(Dubber)指向角色(NarutoRole),那我一开始的理解目标节点应该是NarutoRole,但官网是这样写的

@TargetNode: Applied on a field of a class annotated with @RelationshipProperties to mark the target of that relationship from the perspective of the other end.
度娘翻译:@TargetNode:应用于带有@RelationshipProperties注释的类的字段,以从另一端的角度标记该关系的目标。

其实度娘的翻译也不是很明确,但上面写的从另一端的角度标记该关系的目标,因此可以看到,@TargetNode注解我是放到了Dubber上。

Repository

repository就没啥说的了,就只是继承了Neo4jRepository,简单粘贴一下

@Repository
public interface DubberRepository extends Neo4jRepository<Dubber,String> {
}

@Repository
public interface NarutoRoleRepository extends Neo4jRepository<NarutoRole,Long> {
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

测试类

@SpringBootTest
class Sdn6DemoApplicationTests {

    @Test
    void contextLoads() {
    }

    @Autowired
    NarutoRoleRepository narutoRoleRepository;

    @Autowired
    DubberRepository dubberRepository;


    @Test
    void createNode(){


        NarutoRole narutoRole = narutoRoleRepository.findById(65L).get();

        Dubber dubber = new Dubber("竹内顺子");


        Dubber dubberOp = dubberRepository.findOne(Example.of(dubber)).get();

        narutoRole.getDubTo().add(DubTo.builder().dubber(dubberOp).build());

        narutoRoleRepository.save(narutoRole);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

总结

本案例仅供初学者参考,gitee地址:
https://gitee.com/heheyixiao/neo4j-test-demo

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/641477
推荐阅读
相关标签
  

闽ICP备14008679号