赞
踩
最近看因为工作原因开始接触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本质上是NoSQL,因此开发对Neo4j的学习可能主要集中在CQL上。但我觉得图数据库造数据要比RDB麻烦的多,RDB无非就插入几条数据而已,而且有图形化界面,操作起来很麻烦。
图数据库要造节点,造关系,自带的web图形界面可以查询,插入操作还是要使用CQL语句,一开始对CQL语句操作不熟悉经常把关系搞得很乱,因此我决定导入现成的关系,学起来也比较有意思,有成就感。
想导入数据首先要有数据,网上找了一下没有现成的CSV,那就只能自己做了,我从网上找了一个有意思的,是火影忍者的关系图,内容如下
[ { "entity_1": "千手纲手", "entity_2": "加藤断", "relationship": "女友" }, { "entity_1": "加藤断", "entity_2": "千手纲手", "relationship": "男友" }, { "entity_1": "加藤断", "entity_2": "静音", "relationship": "叔叔" }, //... ]
这种是不能直接导入的,需要改为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,叔叔
...
后面我会把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
也就是类型都是 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
用我这个命令会产生一个naruto.db的库,需要修改配置文件并重启,原因是我的默认库neo4j已经有数据量,而有数据的库是不能用import命令导入的,后文注意第5点有详细说明。
具体的操作是找到conf/neo4j.conf,修改配置文件中的库
# The name of the default database
#dbms.default_database=neo4j
dbms.default_database=naruto.db
最后使用命令重启
neo4j restart
导入数据后部分图(MATCH (n:naruto) RETURN n LIMIT 25)
需要注意以下几点
更多关于Neo4j的操作可以看一下官方文档。
两个案例我会挂到gitee上,因此就不描述目录结构,引入包类等信息了,简单写一下思路和异同点。
新建一个SpringBoot项目,我使用的版本是2.3.5.RELEASE,因为Spring-Data-Neo4j是属于SpringData项目的,因此填写依赖的时候不需要填写它的版本,会根据SpringBoot版本自动设置。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
添加以后,会可以从maven依赖中查看版本。
不同版本推荐写法不同,5.3.5的properties推荐写法
spring.data.neo4j.uri=bolt://localhost:7687
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=123456
创建两个entity类,分别代表节点和边关系。
对应的标签是naruto。
@Data
@NodeEntity(label = "naruto")
public class NarutoRole implements Serializable {
@Id
@GeneratedValue
private Long id;
@Property
private String name;
@Property
private String roleId;
}
注意这里标记节点类是使用@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; }
注意这里的关系类(class)的类型(type)都是narutoRelation,如果想要一种把“师徒”,“对手”这些关系定义为不同的类型(type),需要定义不同的类(类)。
使用@RelationshipEntity代表这是一个边关系类。这里需要两个比较中要的注解@StartNode和@EndNode,这个边关系的起始都是NarutoRole类,如果是不同的类型可以根据情况修改。
Repository简单的多,创建相应的接口继承了Neo4jRepository就可以直接继承相应的增删改查方法,只要注意两个泛型,第一个代表增删改查对应的Entity,第二个代表Entity的id,neo4j自动生成的id都是Long类型,我也建议大家使用Long类型,后面有一次使用了字符串作为Id,自带的findById()方法就不能用了。
@Repository
public interface RelationshipRepository extends Neo4jRepository<NarutoRelation,Long> {}
当然可以也可以执行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);
}
这里也简单说一个知识点,我现在用的版本的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("黑绝","寄生","白绝"); } }
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
火影角色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; }
配音演员Entity
@Node("NarutoDubber")
@Data
public class Dubber {
@Id
private final String name;
}
可以看到这里已经没有@NodeEntity,不是不推荐用,是根本没有这个注解。而且还添加了两个属性作为节点和关系。
然后是关系Entity
@RelationshipProperties
@Data
@Builder
public class DubTo {
@Id
@GeneratedValue
private Long id;
@TargetNode
private final Dubber dubber;
}
这里可以看到用的是注解是@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就没啥说的了,就只是继承了Neo4jRepository,简单粘贴一下
@Repository
public interface DubberRepository extends Neo4jRepository<Dubber,String> {
}
@Repository
public interface NarutoRoleRepository extends Neo4jRepository<NarutoRole,Long> {
}
@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); } }
本案例仅供初学者参考,gitee地址:
https://gitee.com/heheyixiao/neo4j-test-demo
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。