赞
踩
Java操作Neo4j时,有两种运行的模式可以选择:内嵌模式和独立服务器模式。
内嵌模式: 内嵌模式下,数据库会作为你的应用程序的一个部分,与你的应用程序代码在同一个Java进程中运行。你的代码可以直接访问数据库,而无需通过网络进行通信,这使得读写操作的性能非常高。
此外,内嵌模式允许你的应用程序直接访问Neo4j的API,提供更底层的数据库操作。然而,内嵌模式的缺点是它不能支持多个应用程序同时访问数据库,对高并发环境的支持较差。
独立服务器模式: 在独立服务器模式下,Neo4j数据库作为一个独立的服务器运行,你的应用程序通过网络与数据库服务器进行通信。这允许了多个应用程序可以同时连接到数据库服务器,有利于构建高并发、分布式的应用。
此外,数据库服务器可以在与应用程序不同的计算机上运行,使得数据库的维护和扩展更为灵活。然而,由于所有数据库操作都需要通过网络进行,因此在网络延迟或带宽不佳的情况下,数据库操作的性能可能会受到影响。
总的来说:这两种模式各有优劣,应根据你的应用程序的需求和运行环境来选择。如果你需要构建的是一个高性能的单用户应用,或者是一个需要紧密集成数据库的复杂应用,那么内嵌模式可能会是一个更好的选择。而如果你需要构建的是一个分布式、高并发的Web应用,那么独立服务器模式可能更适合你。
Neo4j支持三种网络协议(Protocol) 分别是Bolt,HTTP 和 HTTPS,默认的连接器配置有三种,为了使用这三个端口,需要在Windows防火墙中创建Inbound Rules,允许通过端口7687,7474和7473访问本机
内嵌服务器不需要配置,只要在配置类中指定好数据库的存放路径
不同的版本,使用起来api不同。本文使用都是以 5.12.0 版本进行测试
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j</artifactId>
<version>5.12.0</version>
</dependency>
配置类:基于构建 GraphDatabaseService 的实现类,操作事务。
内嵌模式下,data目录下面会记录锁,不能两个程序操作同一个neo4j 数据库
@Configuration public class Neo4jConfig{ //使用内嵌式数据库 @Bean public GraphDatabaseService graphDatabaseService() { Path directory = null; directory = Paths.get("E:\\DeskTop\\222");// db存放地址 if (Files.notExists(directory, new LinkOption[0])) { try { Files.createDirectory(directory); } catch (IOException var5) { var5.printStackTrace(); } } DatabaseManagementServiceBuilder builder = (new DatabaseManagementServiceBuilder(directory)) .setConfig(GraphDatabaseSettings.transaction_timeout, Duration.ofSeconds(86400L)) .setConfig(GraphDatabaseSettings.preallocate_logical_logs, true) .setConfig( GraphDatabaseSettings.memory_transaction_global_max_size, ByteUnit.gibiBytes(10L)) .setConfig(BoltConnector.enabled, true) .setConfig(BoltConnector.listen_address, new SocketAddress("localhost", 7688));// 暴露一个端口,方便查询 DatabaseManagementService managementService = builder.build(); GraphDatabaseService database = managementService.database("neo4j"); registerShutdownHook(managementService); return database; } private static void registerShutdownHook(final DatabaseManagementService managementService) { Runtime var10000 = Runtime.getRuntime(); Objects.requireNonNull(managementService); var10000.addShutdownHook(new Thread(managementService::shutdown)); } } 注意: .setConfig(BoltConnector.enabled, true) // 启用bolt 协议 .setConfig(BoltConnector.listen_address, new SocketAddress("localhost", 7688));// 暴露端口 增加这两个配置,可以让内置服务器对外暴露一个端口,可以使用 Neo4j-DeskTop 链接,访问内嵌服务的数据
简单使用
=== Cypher 方式 private final GraphDatabaseService graphDB; // ... try ( Transaction tx = graphDB.beginTx();) { for (Document doc : documents) { Map<String, Object> parameters = new HashMap<>(); parameters.put("container", doc.getContainer()); parameters.put("branchOid", doc.getBranchOid()); parameters.put("statusInfo", doc.getStatusInfo()); // Cypher command String query = "CREATE (d:Document {" + "container: $container, " + "branchOid: $branchOid, " + "statusInfo: $statusInfo "+ "})"; tx.execute(query, parameters); } // 批量提交 tx.commit(); } ==== 或者使用 createNode 的方式(Java API ) ==== try ( Transaction tx = graphDB.beginTx()) { int batchSize = documents.size(); // 设置批处理大小 int count = 0; for (Document doc : documents) { Node node = tx.createNode(Label.label("Document")); node.setProperty("container", doc.getContainer()); node.setProperty("branchOid", doc.getBranchOid()); node.setProperty("statusInfo", doc.getStatusInfo()); } tx.commit(); // 提交事务 }
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>5.12.0</version>
</dependency>
配置类:基于构建 org.neo4j.driver.Driver 的实现类,操作事务
// 配置包括服务器的url,用户名、密码 @Configuration public class Neo4jConfig { @Value(value = "${spring.neo4j.uri}") private String noe4jUrl; @Value(value = "${spring.neo4j.authentication.username}") private String username; @Value(value = "${spring.neo4j.authentication.password}") private String password; //使用服务器数据库 @Bean public Session getDrive() { Driver driver = GraphDatabase.driver(noe4jUrl, AuthTokens.basic(username, password)); return driver.session(SessionConfig.builder().withDatabase("neo4j").build()); } }
yml配置
spring:
application:
name: neo4jDemo
neo4j:
uri: bolt://localhost:7687
authentication:
username: neo4j
password: corilead
简单使用
private void methodBatchCypher(List<Document> documents) { try (Transaction tx = session.beginTransaction()) { int batchSize = documents.size(); // 设置批处理大小 int count = 0; List<Map<String, Object>> allParameters = new ArrayList<>(); for (Document doc : documents) { Map<String, Object> parameters = new HashMap<>(); parameters.put("container", doc.getContainer()); parameters.put("branchOid", doc.getBranchOid()); parameters.put("statusInfo", doc.getStatusInfo()); })"; Map<String, Object> batchParameters = Collections.singletonMap("batch", allParameters); tx.run(query, batchParameters); } } tx.commit(); } } // 上面这是利用 session 手动开启一个事务,然后手动提交或者回滚 核心就是 tx.run(...); 这是一个重载的方法 private void methodBatchCypher2(List<Document> documents) { int batchSize = 1000; // 设置批处理大小 int count = 0; List<Map<String, Object>> allParameters = new ArrayList<>(); for (Document doc : documents) { Map<String, Object> parameters = new HashMap<>(); parameters.put("container", doc.getContainer()); parameters.put("branchOid", doc.getBranchOid()); parameters.put("statusInfo", doc.getStatusInfo()); allParameters.add(parameters); count++; if (count % batchSize == 0 || count == documents.size()) { // 执行批处理操作 String query = "UNWIND $batch AS row CREATE (d:Document { " + "container: row.container, " + "branchOid: row.branchOid, " + "statusInfo: row.statusInfo, " + })"; Map<String, Object> batchParameters = Collections.singletonMap("batch", allParameters); session.executeWrite(tx -> { tx.run(query, batchParameters).consume(); // 处理查询结果 return null; // 返回结果 }); // 也可以用 session.executeWrite 执行cql操作,只不过不需要手动控制事务了 // 最后要关闭 Driver和Session,来释放资源
Spring Data Neo4j 也可以用于直接访问服务器模式下的 Neo4j, 类似于Jpa的形式。
注: Spring Data Neo4j 同时支持了服务器模式和内嵌模式。然而在5.x版本之后的Spring Data Neo4j,官方选择了将其定位为一个纯粹的客户端,也就是说,它只支持通过Bolt协议或者Http协议连接到远程的Neo4j服务器,而不再支持直接访问内嵌的Neo4j实例。
这是因为使用内嵌模式的Neo4j更多是在测试环境或者特定的用途中,在生产环境中,我们一般都会使用服务器模式的Neo4j,这样可以更好地利用多核CPU,更有效地进行内存管理,以及提供是集群、HA(高可用性)、备份等特性
目前看使用Java操作neo4j 常用的方式有
基于 5.12.0 <dependency> <groupId>org.neo4j</groupId> <artifactId>neo4j</artifactId> <version>5.12.0</version> </dependency>
- 1
- 2
- 3
- 4
- 5
- 6
基于一个空 的neo4J数据库开始测试。统计插入4w 个Document节点耗时情况
都是采用批处理的方式
数据量(递增) | Spring Data Neo4j(耗时) | Cypher(耗时) | Java API(耗时) |
---|---|---|---|
4w | 513 s | 6.9 s | 7.2 s |
4w | 23 min | 4.8 s | 6.8 s |
4w | - | 4.5 s | 6.7 s |
4w | - | 4.7 s | 7.0 s |
从200 环境拿到数据,目前200 环境的图库中有62w数据,包括节点以及关系
批处理的方式
数据量(递增) | Spring Data Neo4j(耗时) | Cypher(耗时) | Java API(耗时) |
---|---|---|---|
4w | > 1h | 6.2s | 6.8s |
4w | - | 6.3s | 6.6s |
4w | - | 6.3s | 6.5s |
4w | - | 5.5s | 6.5s |
基于空的数据库
数据量(递增) | Spring Data Neo4j(耗时) | Cypher(耗时) | Java API(耗时) |
---|---|---|---|
100条 | 0.6 s | 0.09 s | 0.06 s |
100条 | 0.66 s | 0.08 s | 0.08 s |
100条 | 0.72 s | 0.06s | 0.08 s |
100条 | 0.88 s | 0.06 s | 0.06 s |
基于60w节点
数据量(递增) | Spring Data Neo4j(耗时) | Cypher(耗时) | Java API(耗时) |
---|---|---|---|
100条 | 13.2 s | 0.065 s | 0.062 s |
100条 | 12.6 s | 0.066 s | 0.064 s |
100条 | 13.4 s | 0.069 s | 0.064 s |
100条 | 14.2 s | 0.067 s | 0.066 s |
5.17.0 社区版
注意:
当前springboot 版本为 2.7.11,对应的 spring-boot-starter-data-neo4j 中 关联的 neo4j-java-driver 依赖为4.4 版本,所以api的使用 与内嵌neo4j服务器的5.x 版本会有所区别.
可以单独引入一个 5.12.0 的 neo4j-java-driver的依赖,并排除 spring-boot-starter-data-neo4j 中 包含的driver依赖
使用起来与内嵌模式下的api有部分区别:
数据量(递增) | Spring Data Neo4j(耗时) | Java驱动(耗时) |
---|---|---|
4w | 420s | 5.15 s |
4w | > 20 min | 5.02 s |
4w | - | 5.3 s |
4w | - | 4.7s |
数据量(递增) | Spring Data Neo4j(耗时) | Java驱动(耗时) |
---|---|---|
4w | - | 5s |
4w | - | 4.8s |
4w | - | 5.02s |
4w | - | 4.7s |
基于空的数据库
数据量(递增) | Spring Data Neo4j(耗时) | Java驱动(耗时) |
---|---|---|
100条 | 0.7 s | 0.58s |
100条 | 0.58 s | 0.48s |
100条 | 0.43 s | 0.34s |
100条 | 0.6 s | 0.38s |
基于60w节点数据测试
数据量(递增) | Spring Data Neo4j(耗时) | Java驱动(耗时) |
---|---|---|
100条 | 7.9s | 0.24 |
100条 | 7.5s | 0.18 |
100条 | 7.2s | 0.28 |
100条 | 7.2s | 0.23 |
文件地址可以是网络上的静态资源也可以是本地磁盘上的目录
// import java.util.Map
// import org.neo4j.driver.SessionConfig
try (var session = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
var result = session.run("""
LOAD CSV FROM 'https://data.neo4j.com/bands/artists.csv' AS line
CALL {
WITH line
MERGE (:Artist {name: line[1], age: toInteger(line[2])})
} IN TRANSACTIONS OF 2 ROWS
""");
var summary = result.consume();
System.out.println(summary.counters());
}
将 LOAD CSV 命令在Java中执行
用 Cypher脚本创建节点,批量提交数据。
批量插入数据
数据量(递增) | Spring Data (服务器) | Spring Data(内嵌) | api(服务器) | api(内嵌) | Cypher(内嵌) |
---|---|---|---|---|---|
4w | 420s | 513 s | 5.15 s | 7.2 s | 6.9 s |
4w | > 20 min | 23 min | 5.02 s | 6.8 s | 4.8 s |
4w | - | - | 5.3 s | 6.7 s | 4.5 s |
4w | - | - | 4.7s | 7.0 s | 4.7 s |
循环插入100条
数据量(递增) | Spring Data (服务器) | Spring Data(内嵌) | api(服务器) | api(内嵌) | Cypher(内嵌) |
---|---|---|---|---|---|
100 | 0.7 s | 0.6 s | 0.58s | 0.06 s | 0.09 s |
100 | 0.58 s | 0.66 s | 0.48s | 0.08 s | 0.08 s |
100 | 0.43 s | 0.72 s | 0.34s | 0.08 s | 0.06s |
100 | 0.6 s | 0.88 s | 0.38s | 0.06 s | 0.06 s |
批量插入数据
数据量(递增) | Spring Data (服务器) | Spring Data(内嵌) | api(服务器) | api(内嵌) | Cypher(内嵌) |
---|---|---|---|---|---|
4w | > 1h | > 1h | 5s | 6.8s | 6.2s |
4w | - | - | 4.8s | 6.6s | 6.3s |
4w | - | - | 5.02s | 6.5s | 6.3s |
4w | - | - | 4.7s | 6.5s | 5.5s |
循环插入100条
数据量(递增) | Spring Data (服务器) | Spring Data(内嵌) | api(服务器) | api(内嵌) | Cypher(内嵌) |
---|---|---|---|---|---|
100 | 7.9s | 13.2 s | 0.24 | 0.062 s | 0.065 s |
100 | 7.5s | 12.6 s | 0.18 | 0.064 s | 0.066 s |
100 | 7.2s | 13.4 s | 0.28 | 0.064 s | 0.069 s |
100 | 7.2s | 14.2 s | 0.23 | 0.066 s | 0.067 s |
1、查找Lable 以及对应的数量:
MATCH (n)
WITH LABELS(n) AS labels
UNWIND labels AS label
WITH label, COUNT(*) AS count
RETURN label, count
ORDER BY count DESC
2、创建唯一索引
为Label 以及属性id(4.0 以后的语法)
#创建约束 CREATE CONSTRAINT id_unique_for_wenzi FOR (n:wenzi) REQUIRE n.id IS UNIQUE CREATE CONSTRAINT id_unique_for_wenziMaster FOR (n:wenziMaster) REQUIRE n.id IS UNIQUE CREATE CONSTRAINT id_unique_for_wenziBranch FOR (n:wenziBranch) REQUIRE n.id IS UNIQUE CREATE CONSTRAINT id_unique_for_Document FOR (n:Document) REQUIRE n.id IS UNIQUE CREATE CONSTRAINT id_unique_for_DocumentBranch FOR (n:DocumentBranch) REQUIRE n.id IS UNIQUE CREATE CONSTRAINT id_unique_for_DocumentMaster FOR (n:DocumentMaster) REQUIRE n.id IS UNIQUE CREATE CONSTRAINT id_unique_for_part FOR (n:Part) REQUIRE n.id IS UNIQUE; CREATE CONSTRAINT id_unique_for_part_branch FOR (n:PartBranch) REQUIRE n.id IS UNIQUE; CREATE CONSTRAINT id_unique_for_part_master FOR (n:PartMaster) REQUIRE n.id IS UNIQUE; -- j CREATE CONSTRAINT id_unique_for_group FOR (n:Group) REQUIRE n.id IS UNIQUE; CREATE CONSTRAINT id_unique_for_user FOR (n:USER) REQUIRE n.id IS UNIQUE; CREATE CONSTRAINT id_unique_for_team FOR (n:Team) REQUIRE n.id IS UNIQUE; CREATE CONSTRAINT id_unique_for_folder FOR (n:Folder) REQUIRE n.id IS UNIQUE; // 创建节点索引 CREATE INDEX index_for_document_id FOR (n:Document) ON (n.id); CREATE INDEX index_for_document_branch_id FOR (n:DocumentBranch) ON (n.id); CREATE INDEX index_for_document_master_id FOR (n:DocumentMaster) ON (n.id); CREATE INDEX FOR (n:yanshi) ON (n.id); CREATE INDEX FOR (n:yanshiMaster) ON (n.id); CREATE INDEX FOR (n:yanshiBranch) ON (n.id); CREATE INDEX index_for_part_id FOR (n:Part) ON (n.id); CREATE INDEX index_for_part_branch_id FOR (n:PartBranch) ON (n.id); CREATE INDEX index_for_part_master_id FOR (n:PartMaster) ON (n.id); CREATE INDEX FOR (n:CadDocument) ON (n.id); CREATE INDEX FOR (n:CadDocumentBranch) ON (n.id); CREATE INDEX FOR (n:CadDocumentMaster) ON (n.id); CREATE INDEX FOR (n:DeliveryDocuments) ON (n.id); CREATE INDEX FOR (n:RemainProblem) ON (n.id); CREATE INDEX FOR (n:User) ON (n.id); CREATE INDEX FOR (n:Team) ON (n.id); CREATE INDEX FOR (n:Group) ON (n.id); CREATE INDEX FOR (n:Folder) ON (n.id); CREATE INDEX FOR (n:Container) ON (n.id); CREATE INDEX FOR (n:Tenant) ON (n.id); 创建关系的索引: CREATE INDEX FOR ()-[r:GroupMember]-() ON (r.id) CREATE INDEX FOR ()-[r:TeamMember]-() ON (r.id) CREATE INDEX FOR ()-[r:MEMBER]-() ON (r.id) CREATE INDEX FOR ()-[r:FolderMember]-() ON (r.id) CREATE INDEX FOR ()<-[r:Access]-() ON (r.id) --使用较多 注意节点名称的大小写 CREATE INDEX FOR ()-[r:MASTER_ITERATION]-() ON (r.id)--使用较多 CREATE INDEX FOR ()-[r:BRANCH_ITERATION]-() ON (r.id)--使用较多 创建lookup索引: CREATE LOOKUP INDEX lookup_index_name FOR (n) ON EACH labels(n) 在具有任何标签的节点上创建一个名为 lookup_index_name 的令牌查找索引。 CREATE LOOKUP INDEX FOR ()-[r]-() ON EACH type(r) 在具有任何关系类型的关系上创建标记查找索引。
tail -F /logs/casic2a-plm/casic2a-plm.log | grep “StopWatch” -A 6
tail -F /logs/paas-platform/paas-platform.log | grep “StopWatch” -A 6
match p=(a:PDM2_RENWUSHU{id:'OR:PDM2_RENWUSHU:825642319166308633'})-[*..3]-(b:User{id:'OR:com.corilead.user.User:823821476544708687'})
return p;
// 查询两个节点之间的路径,最大路径按3条查找
MATCH p=(a:PDM2_RENWUSHU{id:'OR:PDM2_RENWUSHU:825642319166308633'})-[*..3]-(b:User{id:'OR:com.corilead.user.User:823821476544708687'})
UNWIND relationships(p) AS r
WITH r
WHERE type(r) = 'Access'
RETURN collect(r) AS AccessRelations;
背景:查询数据节点n和用户之间是否有指定的权限。数据和用户的权限大致分为三类
- 用户节点直接指向数据
- 用户所属组指向数据
- 用户所属容器团队指向数据
注意:优先级依次递减。并且节点和数据之间的关系可能存在多条。这时有一个是拒绝就拿拒绝的权限。
代码思路:
- 用户直接指向节点的话。直接从这里找权限,授予/拒绝。找不到的话查找下一级
- 如果组有直接指向数据的权限,就需要查询这个组和User之间是否存在
GroupMember
的关系。存在的话就可以认为用户和这个数据之间有数据的权限关系。再根据要查询的权限进行查询- 如果用户和组都没有直接指向数据的权限。就需要查询与Team节点关联的所有
Group
节点。它们是通过TeamMember
关联的。然后再查询这些Group节点与User之间是否存在关联。他们的关联关系也是GroupMember
1、最慢的查询:查询节点n以及与他有关的Access关系。
MATCH (n {id:'OR:PDM2_RENWUSHU:827071382446538785'})-[r]-(related)
WHERE labels(related) IN [['Group'], ['Team'], ['User']]
RETURN n, r, related
直接查询所有的权限种类:
1、group
MATCH (a:PDM2_RENWUSHU{id:'OR:PDM2_RENWUSHU:829232340539736099'})<-[R:Access]-(:Group)-[:GroupMember]->(:User{id:'OR:com.corilead.user.User:797010985202483410'})
RETURN R
2、team
MATCH (a:PDM2_RENWUSHU{id:'OR:PDM2_RENWUSHU:829232340539736099'})<-[R:Access]-(:Team)-[:TeamMember]->(:Group)-[:GroupMember]->(:User{id:'OR:com.corilead.user.User:797010985202483410'})
RETURN R
3、user
MATCH (a:PDM2_RENWUSHU{id:'OR:PDM2_RENWUSHU:829232340539736099'})<-[R:Access]-(User{id:'OR:com.corilead.user.User:797010985202483410'})
RETURN R
4、tenant
MATCH (a:PDM2_RENWUSHU{id:'OR:PDM2_RENWUSHU:829232340539736099'})<-[R:Access]-(:Tenant)-[:MEMBER]->(:User{id:'OR:com.corilead.user.User:797010985202483410'})
RETURN R
_RENWUSHU{id:‘OR:PDM2_RENWUSHU:829232340539736099’})<-[R:Access]-(:Team)-[:TeamMember]->(:Group)-[:GroupMember]->(:User{id:‘OR:com.corilead.user.User:797010985202483410’})
RETURN R
3、user
MATCH (a:PDM2_RENWUSHU{id:‘OR:PDM2_RENWUSHU:829232340539736099’})<-[R:Access]-(User{id:‘OR:com.corilead.user.User:797010985202483410’})
RETURN R
4、tenant
MATCH (a:PDM2_RENWUSHU{id:‘OR:PDM2_RENWUSHU:829232340539736099’})<-[R:Access]-(:Tenant)-[:MEMBER]->(:User{id:‘OR:com.corilead.user.User:797010985202483410’})
RETURN R
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。