协调并行事务
使用 .executableQuery()
的书签
当使用 .executableQuery()
查询数据库 时,驱动程序会为您管理书签。在这种情况下,您保证后续查询可以读取以前的更改,无需任何进一步的操作。
driver.executableQuery("<QUERY 1>").execute();
// subsequent .executableQuery() calls will be causally chained
driver.executableQuery("<QUERY 2>").execute(); // can read result of <QUERY 1>
driver.executableQuery("<QUERY 3>").execute(); // can read result of <QUERY 2>
要禁用书签管理和因果一致性,请在查询配置中使用 .withBookmarkManager(null)
。
driver.executableQuery("<QUERY>")
.withConfig(QueryConfig.builder().withBookmarkManager(null).build())
.execute();
单个会话中的书签
在单个会话中运行的查询的书签管理会自动发生,因此您可以相信单个会话内的查询是因果链接的。
try (var session = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
session.executeWriteWithoutResult(tx -> tx.run("<QUERY 1>"));
session.executeWriteWithoutResult(tx -> tx.run("<QUERY 2>")); // can read QUERY 1
session.executeWriteWithoutResult(tx -> tx.run("<QUERY 3>")); // can read QUERY 1,2
}
跨多个会话的书签
如果您的应用程序使用多个会话,您可能需要确保一个会话完成所有事务后,另一个会话才能运行其查询。
在下面的示例中,sessionA
和 sessionB
可以并发运行,而 sessionC
则等待它们的结果传播。这保证了 sessionC
要操作的 Person
节点确实存在。
package demo;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Bookmark;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.SessionConfig;
import org.neo4j.driver.TransactionContext;
public class App {
private static final int employeeThreshold = 10;
public static void main(String... args) {
final String dbUri = "<URI for Neo4j database>";
final String dbUser = "<Username>";
final String dbPassword = "<Password>";
try (var driver = GraphDatabase.driver(dbUri, AuthTokens.basic(dbUser, dbPassword))) {
createSomeFriends(driver);
}
}
public static void createSomeFriends(Driver driver) {
List<Bookmark> savedBookmarks = new ArrayList<>(); // to collect the sessions' bookmarks
// Create the first person and employment relationship
try (var sessionA = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
sessionA.executeWriteWithoutResult(tx -> createPerson(tx, "Alice"));
sessionA.executeWriteWithoutResult(tx -> employ(tx, "Alice", "Wayne Enterprises"));
savedBookmarks.addAll(sessionA.lastBookmarks()); (1)
}
// Create the second person and employment relationship
try (var sessionB = driver.session(SessionConfig.builder().withDatabase("neo4j").build())) {
sessionB.executeWriteWithoutResult(tx -> createPerson(tx, "Bob"));
sessionB.executeWriteWithoutResult(tx -> employ(tx, "Bob", "LexCorp"));
savedBookmarks.addAll(sessionB.lastBookmarks()); (1)
}
// Create a friendship between the two people created above
try (var sessionC = driver.session(SessionConfig.builder()
.withDatabase("neo4j")
.withBookmarks(savedBookmarks) (2)
.build())) {
sessionC.executeWriteWithoutResult(tx -> createFriendship(tx, "Alice", "Bob"));
sessionC.executeWriteWithoutResult(tx -> printFriendships(tx));
}
}
// Create a person node
static void createPerson(TransactionContext tx, String name) {
tx.run("MERGE (:Person {name: $name})", Map.of("name", name));
}
// Create an employment relationship to a pre-existing company node
// This relies on the person first having been created.
static void employ(TransactionContext tx, String personName, String companyName) {
tx.run("""
MATCH (person:Person {name: $personName})
MATCH (company:Company {name: $companyName})
CREATE (person)-[:WORKS_FOR]->(company)
""", Map.of("personName", personName, "companyName", companyName)
);
}
// Create a friendship between two people
static void createFriendship(TransactionContext tx, String nameA, String nameB) {
tx.run("""
MATCH (a:Person {name: $nameA})
MATCH (b:Person {name: $nameB})
MERGE (a)-[:KNOWS]->(b)
""", Map.of("nameA", nameA, "nameB", nameB)
);
}
// Retrieve and display all friendships
static void printFriendships(TransactionContext tx) {
var result = tx.run("MATCH (a)-[:KNOWS]->(b) RETURN a.name, b.name");
while (result.hasNext()) {
var record = result.next();
System.out.println(record.get("a.name").asString() + " knows " + record.get("b.name").asString());
}
}
}
1 | 使用 Session.lastBookmarks() 收集和合并来自不同会话的书签,并将它们存储在 Bookmark 对象中。 |
2 | 使用它们通过 .withBookmarks() 配置方法初始化另一个会话。 |
使用书签可能会对性能产生负面影响,因为所有查询都必须等待最新更改在整个集群中传播。对于简单的用例,尝试将查询分组到单个事务中,或单个会话中。 |
混合 .executableQuery()
和会话
要确保部分使用 .executableQuery()
和部分使用会话执行的事务之间的因果一致性,您可以通过 driver.executableQueryBookmarkManager()
检索 ExecutableQuery
实例的默认书签管理器,并通过 .withBookmarkManager()
配置方法将其传递给新会话。这将确保所有工作都在同一个书签管理器下执行,从而保证因果一致性。
// import org.neo4j.driver.Driver;
// import org.neo4j.driver.SessionConfig;
driver.executableQuery("<QUERY 1>").execute();
try (var session = driver.session(SessionConfig.builder()
.withBookmarkManager(driver.executableQueryBookmarkManager())
.build())) {
// every query inside this session will be causally chained
// (i.e., can read what was written by <QUERY 1>)
session.executeWriteWithoutResult(tx -> tx.run("<QUERY 2>"));
}
// subsequent executableQuery calls will also be causally chained
// (i.e., can read what was written by <QUERY 2>)
driver.executableQuery("<QUERY 3>").execute();
词汇表
- LTS
-
长期支持版本是保证支持数年时间的版本。Neo4j 4.4 是 LTS,Neo4j 5 也将拥有 LTS 版本。
- Aura
-
Aura 是 Neo4j 的完全托管云服务。它提供免费和付费计划。
- Cypher
-
Cypher 是 Neo4j 的图查询语言,允许您从数据库中检索数据。它类似于 SQL,但用于图。
- APOC
-
Cypher 上的优秀过程 (APOC) 是一个(许多)函数的库,这些函数不能轻易在 Cypher 本身中表达。
- Bolt
-
Bolt 是用于 Neo4j 实例和驱动程序之间交互的协议。默认情况下,它监听端口 7687。
- ACID
-
原子性、一致性、隔离性、持久性 (ACID) 是保证数据库事务可靠处理的属性。符合 ACID 的 DBMS 确保数据库中的数据即使在出现故障的情况下也能保持准确和一致。
- 最终一致性
-
如果数据库提供了保证,即所有集群成员将在某个时间点存储数据的最新版本,那么该数据库就是最终一致的。
- 因果一致性
-
如果读写查询在所有集群成员处以相同的顺序出现,则数据库是因果一致的。这比最终一致性更强。
- NULL
-
空标记不是类型,而是值不存在的占位符。有关更多信息,请参阅 Cypher → 使用
null
。 - 事务
-
事务是工作单元,要么全部提交,要么在失败时回滚。一个例子是银行转账:它涉及多个步骤,但必须全部成功或被撤回,以避免从一个账户中扣除资金,但没有添加到另一个账户中。
- 背压
-
背压是反对数据流的力。它确保客户端不会被它无法处理的速度的数据淹没。
- 事务函数
-
事务函数是
executeRead
或executeWrite
调用执行的回调。如果出现服务器故障,驱动程序会自动重新执行回调。 - 驱动程序
-
Driver
对象包含建立与 Neo4j 数据库连接所需的详细信息。