neo4j 开发模式
neo4j开发模式有两种:
- Bolt嵌入式开发模式,提供本地客户端jar包,由java端内嵌调用,操作neo4j数据库。
- 远程REST API模式,neo4j设计之初只考虑java使用,后期为兼容其他语言,增加接口调用。
本人主要使用Bolt模式开发,所以rest api模式不做多讲,感兴趣的同学可以参考官方文档。
REST API模式
主要采用postman工具做演示:
- 采用post模式,请求链接为:http://127.0.0.1:7474/db/data/transaction/commit,选择Basic Auth模式,配置neo4j登录用户名及密码
- 配置请求头信息,Content-Type:application/json
- 输入请求参数,获取当前数据库里面的node数量
{“statements” : [ { “statement” : “match (n) return count(n)” } ]}
- 最终响应
Bolt嵌入式开发
采用spring-boot工程开发,如下贴出主要代码:
- 配置pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.rzx</groupId>
<artifactId>neo4j</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>neo4j</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<fastjson.version>1.2.7</fastjson.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.3.RELEASE</version> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!--springboot 基础 start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!--<scope>test</scope> -->
</dependency>
<!--springboot 基础 end -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>ru.yandex.clickhouse</groupId>
<artifactId>clickhouse-jdbc</artifactId>
<version>0.1.53</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
- 修改application.properties,新增如下配置
spring.data.neo4j.uri=bolt://localhost:7687
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=rzx1218
此时程序已经初始化完毕,接下来就是要开发工作了
开发实操-节点操作
- 新建节点实体类
package com.rzx.neo4j.entity;
import lombok.Getter;
import lombok.Setter;
import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;
/**
* sheeps
*
* @author yangwei
*
*/
@NodeEntity
@Getter
@Setter
public class Sheeps {
@Id
@GeneratedValue
public Long id;
/**
* 名称
*/
private String name;
/**
* 角色
*/
private String role;
@Override
public String toString() {
return "{name:\"" + name + "\",role:\"" + role + "\"" + ",id:" + id + "}";
}
}
- 新建dao层
package com.rzx.neo4j.dao;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;
import com.rzx.neo4j.entity.Sheeps;
@Repository
public interface SheepsDao extends Neo4jRepository<Sheeps, Long> {
@Query(value = "match p=((a:Sheeps{name:{0}})-[*..5]->()) return p",
countQuery= "match p=((a:Sheeps{name:{0}})-[*..5]->()) return count(p)")
Page<Sheeps> findPage(String name, Pageable pageable);
Sheeps findByName(String name);
}
- 新增service层
package com.rzx.neo4j.service;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import com.rzx.neo4j.dao.SheepsDao;
import com.rzx.neo4j.entity.Sheeps;
@Service
public class SheepsService {
@Autowired
private SheepsDao dao;
/**
* 查询所有的sheep数据
* @return sheep数据集合
*/
public List<Sheeps> findAllSheep() {
List<Sheeps> list = new ArrayList<Sheeps>();
dao.findAll().forEach(new Consumer<Sheeps>() {
@Override
public void accept(Sheeps t) {
list.add(t);
}
});
return list;
}
/**
* 根据名称分页查询sheep数据
* @param name 名称
* @return 分页对象
*/
public Page<Sheeps> findPageByName(String name) {
Pageable pageable = PageRequest.of(0, 10);
return dao.findPage(name, pageable);
}
/**
* 新增sheep数据
* @param name
* @param role
*/
public void save(String name,String role) {
Sheeps sheeps = new Sheeps();
sheeps.setName(name);
sheeps.setRole(role);
dao.save(sheeps);
}
/**
* 根据ID删除sheeps数据
* @param id
*/
public void delete(Long id) {
dao.deleteById(id);
}
}
- 操作层
package com.rzx.neo4j.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.rzx.neo4j.entity.Sheeps;
import com.rzx.neo4j.service.SheepsRelationshipService;
import com.rzx.neo4j.service.SheepsService;
/**
* sheeps数据控制层
* @author yangwei
*
*/
@RestController
@RequestMapping("/api/1.0/sheep")
public class SheepsController {
@Autowired
SheepsService sheepsService;
@Autowired
SheepsRelationshipService sheepsRelationshipService;
@GetMapping("/findAllSheep")
public List<Sheeps> findAllSheep() {
return sheepsService.findAllSheep();
}
@GetMapping("/findPageByName")
public Page<Sheeps> findPageByName(String name) {
return sheepsService.findPageByName(name);
}
@GetMapping("/sava")
public void sava(String name, String role) {
sheepsService.save(name, role);
}
@GetMapping("/delete")
public void delete(Long id) {
sheepsService.delete(id);
}
}
以上就完成了对neo4j数据库节点的操作了,主要包含对节点增删改查操作。
开发实操-关系操作
- 新增一个夫妻关系的实体类
package com.rzx.neo4j.entity;
import lombok.Getter;
import lombok.Setter;
import org.neo4j.ogm.annotation.EndNode;
import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.RelationshipEntity;
import org.neo4j.ogm.annotation.StartNode;
@Getter
@Setter
@RelationshipEntity(type = "夫妻")
public class FuqiRelationship {
@Id
@GeneratedValue
private Long id; // 关系id
@StartNode
private Sheeps start; // 开始节点
@EndNode
private Sheeps end; // 结束节点
}
- 新增dao层
package com.rzx.neo4j.dao;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;
import com.rzx.neo4j.entity.FuqiRelationship;
@Repository
public interface FuqiSheepsRelationshipDao extends Neo4jRepository<FuqiRelationship, Long> {
}
- 新增service层
package com.rzx.neo4j.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.neo4j.driver.Record;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.Value;
import org.neo4j.driver.types.Node;
import org.neo4j.driver.types.Path;
import org.neo4j.driver.types.Relationship;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.rzx.neo4j.dao.FuqiSheepsRelationshipDao;
import com.rzx.neo4j.dao.SheepsDao;
import com.rzx.neo4j.entity.FuqiRelationship;
import com.rzx.neo4j.entity.Sheeps;
@Service
public class SheepsRelationshipService {
@Autowired
private FuqiSheepsRelationshipDao dao;
// @Autowired
// private FuziSheepsRelationshipDao fuziSheepsRelationshipDao;
//
// @Autowired
// private XiongdiSheepsRelationshipDao xiongdiSheepsRelationshipDao;
@Autowired
private SheepsDao sheepsDao;
@Autowired
private Session session;
/**
* 新增一个关系
* @param startName 开始节点名称
* @param endName 结束节点名称
* @param relationship 创建关系
*/
public void sava(String startName, String endName, String relationship) {
Sheeps startNode = sheepsDao.findByName(startName);
Sheeps endNode = sheepsDao.findByName(endName);
switch (relationship) {
case "夫妻":
FuqiRelationship sheepsRelationship = new FuqiRelationship();
sheepsRelationship.setEnd(endNode);
sheepsRelationship.setStart(startNode);
dao.save(sheepsRelationship);
break;
// case "父子":
// FuziRelationship fuziRelationship = new FuziRelationship();
// fuziRelationship.setEnd(endNode);
// fuziRelationship.setStart(startNode);
// fuziSheepsRelationshipDao.save(fuziRelationship);
// break;
// case "兄弟":
// XiongdiRelationship xiongdiRelationship = new XiongdiRelationship();
// xiongdiRelationship.setEnd(endNode);
// xiongdiRelationship.setStart(startNode);
// xiongdiSheepsRelationshipDao.save(xiongdiRelationship);
// break;
default:
break;
}
}
/**
* 根据开始节点查询关联关系节点,可指定结束节点查询
* @param startNode 开始节点名称
* @param endNode 结束节点名称
* @return
*/
public Map<String, List<Map<String, Object>>> findByNameAndRelation(String startNode, String endNode) {
String cypherSql = String.format("match r = ((a:Sheeps{name:\"%s\"}) - [*..5] -> ()) return r", startNode);
if (StringUtils.isNotEmpty(endNode)) {
cypherSql = String.format("match (a:Sheeps {name:\"%s\"}) - [b] -> (c:Sheeps {name:\"%s\"}) return a,b,c", startNode, endNode);
}
Result result = session.run(cypherSql);
Map<String, List<Map<String, Object>>> resultMap = new HashMap<String, List<Map<String, Object>>>();
List<Map<String, Object>> nodesList = new ArrayList<Map<String, Object>>();
List<Map<String, Object>> relationshipList = new ArrayList<Map<String, Object>>();
Map<Long, String> nodeIds = new HashMap<Long, String>(); // 保存id,用于去重
Map<Long, String> relationIds = new HashMap<Long, String>(); // 保存id,用于去重
while (result.hasNext()) {
Record record = result.next();
List<Value> values = record.values();
for (Value value : values) {
if ("NODE".equals(value.type().name())) {
Node node = value.asNode();
if (!nodeIds.containsKey(node.id())) {
Map<String, Object> map = setMap(node);
nodesList.add(map);
nodeIds.put(node.id(), "");
}
} else if ("RELATIONSHIP".equals(value.type().name())) {
Relationship relationship = value.asRelationship();
Map<String, Object> map = setMap(relationship);
relationshipList.add(map);
} else if ("PATH".equals(value.type().name())) {
Path path = value.asPath();
Iterable<Node> nodes = path.nodes();
for (Node node : nodes) {
if (!nodeIds.containsKey(node.id())) {
Map<String, Object> map = setMap(node);
nodesList.add(map);
nodeIds.put(node.id(), "");
}
}
Iterable<Relationship> relationships = path.relationships();
for (Relationship relationship : relationships) {
if (!relationIds.containsKey(relationship.id())) {
Map<String, Object> map = setMap(relationship);
relationshipList.add(map);
relationIds.put(relationship.id(), "");
}
}
}
}
}
resultMap.put("relationship", relationshipList);
resultMap.put("nodes", nodesList);
relationIds.clear();
relationIds = null;
nodeIds.clear();
nodeIds = null;
return resultMap;
}
/**
* 设置关系map
* @param relationship
* @return
*/
private Map<String, Object> setMap(Relationship relationship) {
Map<String, Object> map = new HashMap<String, Object>();
map.putAll(relationship.asMap());
map.put("type", relationship.type());
map.put("id", relationship.id());
map.put("startNodeId", relationship.startNodeId());
map.put("endNodeId", relationship.endNodeId());
return map;
}
/**
* 设置节点map
* @param node
* @return
*/
private Map<String, Object> setMap(Node node) {
Map<String, Object> map = new HashMap<String, Object>();
map.putAll(node.asMap());
map.put("id", node.id());
return map;
}
/**
* 删除节点关系
* @param id
*/
public void delete(Long id) {
dao.deleteById(id);
// xiongdiSheepsRelationshipDao.deleteById(id);
// fuziSheepsRelationshipDao.deleteById(id);
}
}
- 新增控制层
package com.rzx.neo4j.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.rzx.neo4j.entity.Sheeps;
import com.rzx.neo4j.service.SheepsRelationshipService;
import com.rzx.neo4j.service.SheepsService;
/**
- sheeps关系数据控制层
- @author yangwei
*/
@RestController
@RequestMapping("/api/1.0/sheep")
public class SheepsController {
@Autowired
SheepsRelationshipService sheepsRelationshipService;
@GetMapping("/savaRelation")
public void savaRelation(String startName, String endName, String relationship) {
sheepsRelationshipService.sava(startName, endName, relationship);
}
@GetMapping("/deleteRelation")
public void deleteRelation(Long id) {
sheepsRelationshipService.delete(id);
}
@GetMapping("/findByNameAndRelation")
public Object findByNameAndRelation(String startNode, String endNode) {
return sheepsRelationshipService.findByNameAndRelation(startNode, endNode);
}
}
复杂关系查询或复杂命令定制开发
节点间的增删改比较简单,都是一对一操作,而比较复杂的操作,spring-jpa-data不太擅长,则需要补充功能
- 新增一个类,创建neo4j session对象
package com.rzx.neo4j.config;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.Session;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Neo4jSourceConfig {
@Value("${spring.data.neo4j.uri}")
private String url;
@Value("${spring.data.neo4j.username}")
private String username;
@Value("${spring.data.neo4j.password}")
private String password;
@Bean(name = "session")
public Session neo4jSession() {
Driver driver = GraphDatabase.driver(url, AuthTokens.basic(username, password));
return driver.session();
}
}
- service层定制开发
@Autowired
private Session session;
/**
* 根据开始节点查询关联关系节点,可指定结束节点查询
* @param startNode 开始节点名称
* @param endNode 结束节点名称
* @return
*/
public Map<String, List<Map<String, Object>>> findByNameAndRelation(String startNode, String endNode) {
String cypherSql = String.format("match r = ((a:Sheeps{name:\"%s\"}) - [*..5] -> ()) return r", startNode);
if (StringUtils.isNotEmpty(endNode)) {
cypherSql = String.format("match (a:Sheeps {name:\"%s\"}) - [b] -> (c:Sheeps {name:\"%s\"}) return a,b,c", startNode, endNode);
}
Result result = session.run(cypherSql);
// 最终汇聚结果集
Map<String, List<Map<String, Object>>> resultMap = new HashMap<String, List<Map<String, Object>>>();
// node节点集合
List<Map<String, Object>> nodesList = new ArrayList<Map<String, Object>>();
// 关系节点集合
List<Map<String, Object>> relationshipList = new ArrayList<Map<String, Object>>();
Map<Long, String> nodeIds = new HashMap<Long, String>(); // 保存id,用于去重
Map<Long, String> relationIds = new HashMap<Long, String>(); // 保存id,用于去重
while (result.hasNext()) {
Record record = result.next();
List<Value> values = record.values();
for (Value value : values) {
// 节点数据解析
if ("NODE".equals(value.type().name())) {
Node node = value.asNode();
if (!nodeIds.containsKey(node.id())) {
Map<String, Object> map = setMap(node);
nodesList.add(map);
nodeIds.put(node.id(), "");
}
// 关系数据解析
} else if ("RELATIONSHIP".equals(value.type().name())) {
Relationship relationship = value.asRelationship();
Map<String, Object> map = setMap(relationship);
relationshipList.add(map);
// 关联路径数据解析,包含节点数据和节点关系数据
} else if ("PATH".equals(value.type().name())) {
Path path = value.asPath();
// 获取节点数据,并解析
Iterable<Node> nodes = path.nodes();
for (Node node : nodes) {
if (!nodeIds.containsKey(node.id())) {
Map<String, Object> map = setMap(node);
nodesList.add(map);
nodeIds.put(node.id(), "");
}
}
// 获取关系数据并解析
Iterable<Relationship> relationships = path.relationships();
for (Relationship relationship : relationships) {
if (!relationIds.containsKey(relationship.id())) {
Map<String, Object> map = setMap(relationship);
relationshipList.add(map);
relationIds.put(relationship.id(), "");
}
}
}
}
}
resultMap.put("relationship", relationshipList);
resultMap.put("nodes", nodesList);
relationIds.clear();
relationIds = null;
nodeIds.clear();
nodeIds = null;
return resultMap;
}
数据导入
- 节点CSV数据文件准备
建立节点关系的数据,在标题上一定要加上:ID,导入时默认是String类型,非string类型的数据注意在头上标明类型 - 关系CSV数据文件准备
关系标题上把要加上的属性标明,不要加: - 最后执行导入命令,默认导入到data/databases/graph.db数据库,导入前一定要删除该目录,或者指定database。导入日志会生成在当前目录的import.report文件中
./bin/neo4j-admin import --nodes:BasicNode import/nodes.csv --relationships import/relation.csv --ignore-extra-columns=true --ignore-missing-nodes=true --ignore-duplicate-nodes=true
小结:对于单个节点、关系的创建使用spring-jpa-data还是非常轻松的,对于复杂查询或者操作来说的话它的扩展性确实很麻烦,不清楚有没有组件或者工具能解决这个问题,再一个就算需要研究一下批量导入数据的功能,因为单个插入的性能确实不强,后面有这方面的突破我会持续更新,未完待续。。。
最后,附上安装包及源码:https://download.csdn.net/download/weixin_37727274/12534808