Neo4j 4.x 社区版数据导入及Spring-Data-Neo4j 5.x、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": "叔叔"
},
//...
]
这种是不能直接导入的,需要改为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)
需要注意以下几点
- 在windows环境下,在CMD输入命令可以直接用命令,但在GitBash中,需要使用neo4j-admin.bat。
- –nodes,–relationships后面是等号(=),3.x是冒号(:)
- –nodes,–relationships后面的路径,并不是你现在所在目录的相对路径,而是以neo4j的根目录作为起始的相对目录,真正执行的时候会拼接上neo4j所在的目录,因此我建议使用时就使用import开头。
- import 命令必须导入到一个空库,如果你的库已经有数据,是无法导入成功的,这也是我在–database=后面加了一个新数据库的原因。
- 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>
添加以后,会可以从maven依赖中查看版本。
配置文件
不同版本推荐写法不同,5.3.5的properties推荐写法
spring.data.neo4j.uri=bolt://localhost:7687
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=123456
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;
}
注意这里标记节点类是使用@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
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("黑绝","寄生","白绝");
}
}
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
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;
}
配音演员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
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