该文的实现有更简单的方式,详见我的另一篇博客springboot整合neo4j–采用Neo4jClient和Neo4jTemplate方式
Neo4j 提供 JAVA API 以编程方式执行所有数据库操作。它支持三种类型的API:
1、Neo4j 原生的 Java API
原生 Java API 是一种低级别的纯 JAVA API,用于执行数据库操作。
2、Neo4j Cypher Java API
Cypher Java API 是简单而强大的 JAVA API,用于执行所有CQL命令以执行数据库操作。
3、Neo4j OGM JAVA API
OGM Java API和Mybatis、JPA类似可以直接和SpringData整合
目前很多项目针对neo4j的操作采用Neo4j OGM JAVA API,该方案与springboot整合之后可以使用@Query注解很方便的编写查询接口,比如这篇SpringBoot集成neo4j实战。
但是这种方式对图数据库的增删改查均依赖业务数据对象(就是你的Java bean对象,比如People、Company),如果我们数据类型很多且不断变化,那这种方式就不再适合。
而Neo4j Cypher Java API对neo4j的操作是业务数据对象无关的,不管是什么样的节点数据或关系数据均可以操作。因为它是直接使用session执行cyhper语句。具体区别可自行查看下方代码。
1.依赖
<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
<artifactId>chaos</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.0</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
<!--<exclusions>
<exclusion>
<artifactId>neo4j-java-driver</artifactId>
<groupId>org.neo4j.driver</groupId>
</exclusion>
</exclusions>-->
</dependency>
<!-- <dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>4.2.0</version>
</dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<excludes>
<!--<exclude>**/*-->
1.Neo4jClientConfig
package com.dbs.neo4j.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "com.dbs.neo4j")
public class Neo4jClientConfig {
private String uri;
private String username;
private String password;
}
2.Neo4jClient
package com.win.chaos.service;
import com.win.chaos.config.Neo4jConfig;
import com.win.chaos.exception.Neo4jException;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.driver.*;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class Neo4jClientService {
private final Driver driver;
public Neo4jClientService(Neo4jConfig neo4jConfig) {
this.driver = GraphDatabase.driver(neo4jConfig.getUri(), AuthTokens.basic(neo4jConfig.getUsername(), neo4jConfig.getPassword()));
}
/**
* 适用执行写逻辑
* @param cypher cypher
* @return Result 由于return之前session已经被关闭,该result不能被消费
* @throws Neo4jException 执行cypher出现异常
*/
public Result run(String cypher) throws Neo4jException {
Session session = driver.session();
Transaction ts = session.beginTransaction();
try {
Result result = ts.run(cypher);
ts.commit();
return result;
} catch (Exception e) {
ts.rollback();
log.error("run neo4j cypher error with ", e);
throw new Neo4jException("run " + cypher + " error with" + e.getMessage());
}finally {
ts.close();
session.close();
}
}
/**
* 用于执行读或写cypher语句
* @param gql cypher
* @return Result
* @throws Neo4jException 执行cypher出现异常
*/
public Result exec(String gql) throws Neo4jException {
try {
Session session = driver.session();
log.info("exec {}", gql);
return session.run(gql);
} catch (Exception e) {
log.error("execute gql {} error ", gql, e);
throw new Neo4jException("execute " + gql + " error with" + e.getMessage());
}
}
}
3.Controller
//查询节点
@GetMapping("/getNode")
public Neo4jGraph getNode() throws Neo4jException {
String ql = "MATCH (n:`公司`) RETURN n LIMIT 25";
StatementResult result = neo4jClient.run(ql);
return Neo4jGraph.parse(result);
}
//查询路径
@GetMapping("/matchPath")
public Neo4jGraph matchPath() throws Neo4jException {
String ql = "MATCH p=()-[r:`持股`]->() RETURN p LIMIT 25";
StatementResult result = neo4jClient.run(ql);
return Neo4jGraph.parse(result);
}
//测试创建和删除
@GetMapping("/test")
public void add() throws Neo4jException {
String addQL = "CREATE (o:people {name:\"里斯\",id:32435})";
StatementResult addResult = neo4jClient.run(addQL);
String delQL = "match (n:people) delete n";
StatementResult delResult = neo4jClient.run(delQL);
}
以上代码实现了springboot整合neo4j原生cypher,可以进行业务数据对象无关的操作。
代码中的Neo4jGraph.parse为解析查询对象接口。如有需要请看下面代码。
4.Neo4jGraph
package com.win.chaos.model.neo4j;
import cn.hutool.core.collection.ConcurrentHashSet;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.driver.Record;
import org.neo4j.driver.Result;
import org.neo4j.driver.Value;
import org.neo4j.driver.internal.types.TypeConstructor;
import org.neo4j.driver.types.Node;
import org.neo4j.driver.types.Path;
import org.neo4j.driver.types.Relationship;
import org.neo4j.driver.types.Type;
import java.util.List;
import java.util.Set;
@Data
@Slf4j
public class Neo4jGraph {
private Set<Neo4jNode> nodes = new ConcurrentHashSet<>();
private Set<Neo4jEdge> edges = new ConcurrentHashSet<>();
/**
* 解析查询结果,目前仅解析节点(Node)、关系(Relationship)、路径(Path)三种查询结果类型
* @param result 查询结果集
* @return Neo4jGraph
*/
public static Neo4jGraph parse(Result result) {
if (result == null) return null;
Neo4jGraph graph = new Neo4jGraph();
List<Record> records = result.list();
for (Record record:records){
List<Value> values = record.values();
for (Value value : values) {
Type type = value.type();
if (type.name().equals(TypeConstructor.NODE.name())) {
Node node = value.asNode();
graph.addNeo4jNode(new Neo4jNode(node));
}else if (type.name().equals(TypeConstructor.PATH.name())) {
Path path = value.asPath();
path.nodes().forEach(node -> graph.addNeo4jNode(new Neo4jNode(node)));
path.relationships().forEach(relationship -> graph.addNeo4jEdge(new Neo4jEdge(relationship)));
}else if (type.name().equals(TypeConstructor.RELATIONSHIP.name())) {
Relationship relationship = value.asRelationship();
graph.addNeo4jEdge(new Neo4jEdge(relationship));
}else {
log.error("目前不支持{}类型的查询数据解析。", type.name());
}
}
}
return graph;
}
public void addNeo4jNode(Neo4jNode neo4jNode) {
nodes.add(neo4jNode);
}
public void addNeo4jEdge(Neo4jEdge neo4jEdge) {
edges.add(neo4jEdge);
}
}
5.Neo4jNode
package com.win.chaos.model.neo4j;
import lombok.Data;
import org.neo4j.driver.types.Node;
import java.util.*;
@Data
public class Neo4jNode {
private Object id;
private List<String> labels = new ArrayList<>();
private Map<String, Object> props = new HashMap<>();
public Neo4jNode(Node node) {
id = node.id();
parseLabels(node);
parseProp(node);
}
public void parseLabels(Node node){
if (node.labels() != null){
node.labels().forEach(labels::add);
}
}
public void parseProp(Node node) {
Map<String, Object> propsMap = node.asMap();
Set<String> keys = propsMap.keySet();
for (String key : keys){
props.put(key, propsMap.get(key));
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Neo4jNode vertex = (Neo4jNode) o;
return id.equals(vertex.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}
6.Neo4jEdge
package com.win.chaos.model.neo4j;
import lombok.Data;
import org.neo4j.driver.types.Relationship;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Data
public class Neo4jEdge {
private long edgeId;
private Object srcId;
private Object dstId;
private String label;
private Map<String, Object> props = new HashMap<>();
public Neo4jEdge(Relationship relationship) {
srcId = relationship.startNodeId();
dstId = relationship.endNodeId();
edgeId = relationship.id();
label = relationship.type();
parseProps(relationship);
}
private void parseProps(Relationship relationship) {
Map<String, Object> data = relationship.asMap();
Set<String> keys = data.keySet();
for (String key : keys) {
props.put(key, data.get(key));
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Neo4jEdge that = (Neo4jEdge) o;
//if (!edgeId.equals(that.edgeId)) return false;
if (!srcId.equals(that.srcId)) return false;
if (!dstId.equals(that.dstId)) return false;
return label.equals(that.label);
}
@Override
public int hashCode() {
int result = srcId.hashCode();
result = 31 * result + dstId.hashCode();
result = 31 * result + label.hashCode();
result = 31 * result + (int) (edgeId ^ (edgeId >>> 32));
return result;
}
}
完整代码:springboot_neo4j_hanlp
图数据库 Neo4j Java Api 的使用