Neo4j-SpringBoot简单操作
- Neo4j
- Neo4j安装
- 数据导入Neo4j
- SpringBoot 整合neo4j
- Neo4j 字符串转列表
- 持续学习
Neo4j
Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中。它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎,但是它将结构化数据存储在网络(从数学角度叫做图)上而不是表中。Neo4j也可以被看作是一个高性能的图引擎,该引擎具有成熟数据库的所有特性。程序员工作在一个面向对象的、灵活的网络结构下而不是严格、静态的表中——但是他们可以享受到具备完全的事务特性、企业级的数据库的所有好处。
Neo4j图数据库遵循属性图模型来存储和管理其数据。属性图由点(节点)、边(关系)和属性三者组成。可以为点设置不同标签,边也可以分为很多种类型。点和边可以有多个属性,属性以kv键值对的方式表示。边是必需有方向的,neo4j边只能是单向的,边的方向不影响查询的效率。
Neo4j是一个无架构数据库。在开始添加数据之前,你并不需要定义表和关系。一个节点可以具有你喜欢的任何属性,任何节点都可以与其他任何节点建立关系。Neo4j数据库中的数据模型隐含在它存储的数据中,而不是明确地将数据模型定义为数据库本身的一个部分。它是对你想要存入数据库的数据的一个描述,而不是数据库的一系列方法来限制将要存储的内容。
Neo4j安装
Neo4j官方下载,小提示:
- Neo4j需要合适版本的jdk支持(neo4j 4以后的版本需求jdk11以上,亲测jdk17不行);
- neo4j启动直接用neo4j.bat console即可,无需下载本地服务;
- 按照官方建议下载jdk11和neo4j 4版本可能更佳(笔者是用的jdk8和neo4j 3.53);
- 详细安装步骤网上很多,这里就不赘述了;
数据导入Neo4j
导入方式:
CREATE语句 | LOAD CSV语句 | Batch Inserter | Batch Import | Neo4j-import | |
适用场景 | 1 ~ 1w nodes | 1w ~ 10 w nodes | 千万以上 nodes | 千万以上 nodes | 千万以上 nodes |
速度 | 很慢 (1000 nodes/s) | 一般 (5000 nodes/s) | 非常快 (数万 nodes/s) | 非常快 (数万 nodes/s) | 非常快 (数万 nodes/s) |
优点 | 使用方便,可实时插入 | 使用方便,可以加载本地/远程CSV;可实时插入 | 速度相比于前两个,有数量级的提升 | 基于Batch Inserter,可以直接运行编译好的jar包;可以在已存在的数据库中导入数据 | 官方出品,比Batch Import占用更少的资源 |
缺点 | 速度慢 | 需要将数据转换成CSV | 需要转成CSV;只能在JAVA中使用;且插入时必须停止neo4j | 需要转成CSV;必须停止neo4j | 需要转成CSV;必须停止neo4j;只能生成新的数据库,而不能在已存在的数据库中插入数据。 |
导入数据:
neo4j-admin import --database demo.db --nodes:Company test_5.csv
修改neo4j配置文件:
打开neo4j浏览器可视化界面查看数据:
SpringBoot 整合neo4j
- 小提示:
- Springboot有jar包内置了neo4j bolt 驱动,且方便调试维护
- Springboot版本必须和neo4j版本对应,否则依赖jar包会有问题
- 这里使用的neo4j版本为3.5.34(4.*版本需要11及以后的jdk,但是我的17版本不行)
- 一定要修改Spirngboot的版本号!
- version
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
- entity
package com.zzx.neo4jspringboot4.entity;
import lombok.Data;
import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Property;
import java.io.Serializable;
import java.util.List;
@Data
@NodeEntity(label = "Company")
public class Company implements Serializable {
@Id
@GeneratedValue
private Long id;
@Property
private String PID;
@Property
private String district;
@Property
private String ENTNAME;
@Property
private String short_name;
@Property
private String DOM;
@Property
private List<String> CTBRANCHINFO;
@Property
private List<String> INVEST;
@Property
private List<String> SHAREHOLDER;
@Property
private List<String> PERSONLIST;
@Property
private String PERSONKVS;
}
package com.zzx.neo4jspringboot4.entity;
import lombok.Data;
import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Property;
import java.io.Serializable;
@Data
@NodeEntity(label = "Person")
public class Person implements Serializable {
@Id
@GeneratedValue
private Long id;
@Property
private String name;
}
package com.zzx.neo4jspringboot4.entity;
import lombok.Data;
import org.neo4j.ogm.annotation.*;
import java.io.Serializable;
@Data
@RelationshipEntity(type = "PersonRelation")
public class PersonRelation implements Serializable {
@Id
@GeneratedValue
private Long id;
@StartNode
private Person parent;
@EndNode
private Company child;
@Property
private String relation;
}
package com.zzx.neo4jspringboot4.entity;
import lombok.Data;
import org.neo4j.ogm.annotation.*;
import org.springframework.stereotype.Component;
import java.util.List;
@Data
@RelationshipEntity(type = "同名高管")
public class Relation {
@Id
@GeneratedValue
private Long id;
@StartNode
private Company parent;
@EndNode
private Company child;
@Property
private List<String > PERSONS;
}
- dao
package com.zzx.neo4jspringboot4.dao;
import com.zzx.neo4jspringboot4.entity.Company;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CompanyRepository extends Neo4jRepository<Company,Long> {
@Query("match(n:Company{PID:{PID}}) return n.ENTNAME")
String existsPID(String PID);
@Query("MATCH (n:Company) WITH n AS n,(CASE WHEN apoc.meta.type(n.CTBRANCHINFO)=\"STRING\" and n.CTBRANCHINFO is not null THEN apoc.convert.fromJsonList(n.CTBRANCHINFO) ELSE n.CTBRANCHINFO END) AS CTBRANCHINFO,(CASE WHEN apoc.meta.type(n.INVEST)=\"STRING\" and n.INVEST is not null THEN apoc.convert.fromJsonList(n.INVEST) ELSE n.INVEST END) AS INVEST,(CASE WHEN apoc.meta.type(n.SHAREHOLDER)=\"STRING\" and n.SHAREHOLDER is not null THEN apoc.convert.fromJsonList(n.SHAREHOLDER) ELSE n.SHAREHOLDER END) AS SHAREHOLDER,(CASE WHEN apoc.meta.type(n.PERSONLIST)=\"STRING\" and n.PERSONLIST is not null THEN apoc.convert.fromJsonList(n.PERSONLIST) ELSE n.PERSONLIST END) AS PERSONLIST SET n.CTBRANCHINFO =CTBRANCHINFO,n.INVEST=INVEST,n.PERSONLIST=PERSONLIST,n.SHAREHOLDER=SHAREHOLDER")
void toArray();
@Query("MATCH (c:Company),(m:Company) WHERE c.short_name=m.short_name and c.PID<>m.PID and NOT (m)-[:相同简称]->(c) CREATE (c) -[r:相同简称{type:0,rel:0.7}]->(m)")
void creatRelation_short_name();
@Query("MATCH (c:Company),(m:Company) WHERE c.DOM=m.DOM and c.PID<>m.PID and NOT (m)-[:相同工商注册地址]->(c) CREATE (c) -[r:相同工商注册地址{type:0,rel:0.8}]->(m)")
void creatRelation_DOM();
@Query("MATCH (c:Company),(m:Company) WHERE c.ENTNAME IN m.CTBRANCHINFO and c.PID<>m.PID CREATE (c) -[r:分支机构{type:0,rel:1}]->(m)")
void creatRelation_CTBRANCHINFO();
@Query("MATCH (c:Company),(m:Company) WHERE c.ENTNAME IN m.INVEST and c.PID<>m.PID CREATE (m) -[r:对外投资{type:0,rel:1}]->(c)")
void creatRelation_INVEST();
@Query("MATCH (c:Company),(m:Company) WHERE c.ENTNAME IN m.SHAREHOLDER and c.PID<>m.PID CREATE (c) -[r:股东{type:0,rel:1}]->(m)")
void creatRelation_SHAREHOLDER();
@Query("MATCH (c:Company),(m:Company) WHERE c.PERSONLIST is not null and m.PERSONLIST is not null and c.PID<>m.PID and length(c.PERSONLIST)>0 and length(m.PERSONLIST)>0 WITH c AS c,m AS m, (FILTER( x in c.PERSONLIST WHERE x in m.PERSONLIST and x<>\"NULL\")) AS list WHERE length(list)>0 and NOT (m)-[:同名高管]->(c) WITH c AS c,m AS m, (CASE WHEN (CASE WHEN (c)-[]-(m) THEN 1 ELSE 0 END)+length(list)+(CASE WHEN c.district = m.district THEN 1 ELSE 0 END)>1 THEN 1 ELSE 2 END) AS type,list AS list CREATE (c)-[r:同名高管]->(m) SET r.type =type,r.PERSONS=list")
void creatRelation_PERSONS();
@Query("MATCH (c:Company)-[r]->(m:Company) WHERE r.type<>2 and NOT (m)-[:直接关系]-(c) MERGE (c) -[re:直接关系]->(m)")
void creatRelation_DIRECT();
@Query("MATCH (c:Company)-[r:同名高管{type:2}]->(m:Company) WHERE (c)-[:直接关系*2..6]-(m) SET r.type = 1")
void changeType();
}
package com.zzx.neo4jspringboot4.dao;
import com.zzx.neo4jspringboot4.entity.PersonRelation;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PersonRelationRepository extends Neo4jRepository<PersonRelation,Long> {
}
package com.zzx.neo4jspringboot4.dao;
import com.zzx.neo4jspringboot4.entity.Person;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface PersonRepository extends Neo4jRepository<Person,Long> {
@Query("MATCH (n:Person{name:{name}}) RETURN count(n)>0")
Boolean findByName(@Param("name") String name);
}
package com.zzx.neo4jspringboot4.dao;
import com.zzx.neo4jspringboot4.entity.Relation;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
@Repository
public interface RelationRepository extends Neo4jRepository<Relation,Long> {
@Query("MATCH (c:Company)-[r:同名高管{type:1}]-(m:Company) RETURN r,startNode(r),endNode(r)")
ArrayList<Relation> getRelationType1();
@Query("MATCH (c:Company)-[r:直接关系]-(m:Company) DELETE r")
void deleteRelation_DIRECT();
@Query("MATCH (c:Company)-[r:直接关系]-(m:Company) DELETE r")
void deleteType1();
}
- Test
package com.zzx.neo4jspringboot4;
import com.zzx.neo4jspringboot4.dao.CompanyRepository;
import com.zzx.neo4jspringboot4.dao.PersonRelationRepository;
import com.zzx.neo4jspringboot4.dao.PersonRepository;
import com.zzx.neo4jspringboot4.dao.RelationRepository;
import com.zzx.neo4jspringboot4.entity.Company;
import com.zzx.neo4jspringboot4.entity.Person;
import com.zzx.neo4jspringboot4.entity.PersonRelation;
import com.zzx.neo4jspringboot4.entity.Relation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.alibaba.fastjson.JSONObject;
@SpringBootTest
public class DataTest {
@Autowired
CompanyRepository companyRepository;
@Autowired
RelationRepository relationRepository;
@Autowired
PersonRepository personRepository;
@Autowired
PersonRelationRepository personRelationRepository;
@Test
public void fun(){
System.out.println("neo4j ,启动!!!");
int start = 0;
int end = 10;
for(int i =start;i<=end;i++){
long startTime = new Date().getTime();
cypherWith(i);
long time = new Date().getTime()-startTime;
System.out.println("===================已完成第--"+i+"--步,耗时:"+time+"毫秒===================");
}
}
private void cypherWith(int i){
switch (i){
case 0:
companyRepository.toArray();
break;
case 1:
companyRepository.creatRelation_short_name();
break;
case 2:
companyRepository.creatRelation_DOM();
break;
case 3:
companyRepository.creatRelation_CTBRANCHINFO();
break;
case 4:
companyRepository.creatRelation_INVEST();
break;
case 5:
companyRepository.creatRelation_SHAREHOLDER();
break;
case 6:
companyRepository.creatRelation_PERSONS();
break;
case 7:
companyRepository.creatRelation_DIRECT();
break;
case 8:
companyRepository.changeType();
break;
case 9:
relationRepository.deleteRelation_DIRECT();
case 10:
ArrayList<Relation> relationTypes = relationRepository.getRelationType1();
for (Relation relation : relationTypes){
createNode(relation);
}
break;
default:
System.out.println("=======错误:"+i);
break;
}
}
private void createNode(Relation relation){
Company parent = relation.getParent();
Company child = relation.getChild();
List<String> persons = relation.getPERSONS();
JSONObject jsonParent = new JSONObject();
JSONObject jsonChild = new JSONObject();
try{
jsonParent = JSONObject.parseObject(parent.getPERSONKVS());
}catch (Exception e){
e.printStackTrace();
}
try{
jsonChild = JSONObject.parseObject(child.getPERSONKVS());
}catch (Exception e){
e.printStackTrace();
}
if (jsonParent.size()<1 || jsonChild.size()<1 || persons.size()<1){
return;
}
for (String person : persons){
Person p = new Person();
p.setName(person);
// if (personRepository.findByName(person)){
// continue;
// }
personRepository.save(p);
System.out.println("创建人物节点成功:"+p.getName());
try{
PersonRelation personRelation = new PersonRelation();
personRelation.setParent(p);
personRelation.setChild(parent);
personRelation.setRelation(jsonParent.get(person).toString());
personRelationRepository.save(personRelation);
System.out.println("创建人物公司关系成功:"+p.getName()+"=="+personRelation.getRelation()+"==>"+parent.getENTNAME());
}catch (Exception e){
e.printStackTrace();
}
try{
PersonRelation personRelation = new PersonRelation();
personRelation.setParent(p);
personRelation.setChild(child);
personRelation.setRelation(jsonChild.get(person).toString());
personRelationRepository.save(personRelation);
System.out.println("创建人物公司关系成功:"+p.getName()+"=="+personRelation.getRelation()+"==>"+child.getENTNAME());
}catch (Exception e){
e.printStackTrac
Neo4j 字符串转列表
遗憾的是原生的Cypher貌似没有直接修改属性由字符串转列表的方法,我们使用neo4j的扩展库
- apoc 字符串转列表操作
MATCH (n:Company) WITH n AS n,(CASE WHEN apoc.meta.type(n.CTBRANCHINFO)="STRING" and n.CTBRANCHINFO is not null THEN apoc.convert.fromJsonList(n.CTBRANCHINFO) ELSE n.CTBRANCHINFO END) AS CTBRANCHINFO,(CASE WHEN apoc.meta.type(n.INVEST)="STRING" and n.INVEST is not null THEN apoc.convert.fromJsonList(n.INVEST) ELSE n.INVEST END) AS INVEST,(CASE WHEN apoc.meta.type(n.SHAREHOLDER)="STRING" and n.SHAREHOLDER is not null THEN apoc.convert.fromJsonList(n.SHAREHOLDER) ELSE n.SHAREHOLDER END) AS SHAREHOLDER,(CASE WHEN apoc.meta.type(n.PERSONLIST)="STRING" and n.PERSONLIST is not null THEN apoc.convert.fromJsonList(n.PERSONLIST) ELSE n.PERSONLIST END) AS PERSONLIST SET n.CTBRANCHINFO =CTBRANCHINFO,n.INVEST=INVEST,n.PERSONLIST=PERSONLIST,n.SHAREHOLDER=SHAREHOLDER