对象映射
简介
有许多不同的工具可以使用 JDBC 进行对象‑关系映射(ORM、O/RM 和 O/R 映射工具),Neo4j JDBC 驱动支持其中的一部分,例如 MyBatis,或使用 JDBI、Spring JDBC Template 等工具进行基于结果集/元组的映射。然而,这些工具常常会生成 SQL 查询,并且往往依赖非常特定的 SQL 功能,而这些功能我们无法在 SQL 到 Cypher® 翻译 中完全实现,因此实际效果可能因情况而异。
| 在 Neo4j 中进行对象映射的最“图形化”方式是使用 Neo4j-OGM(提供对 Spring 和 Quarkus 的集成),或 Spring Data Neo4j。两者均基于 通用 Neo4j Java Driver 构建。如果您需要一个包括仓库支持、先进的映射和查询能力的端到端解决方案,这两种方案都是首选。 |
有时,简单的方案已经足够,而一种已经可用的解决方案是将图数据直接映射为 JSON 对象并在查询中传回 JSON 对象。JDBC 规范允许通过 ResultSet 和 PreparedStatement 类型获取和设置任意类型的对象,Neo4j JDBC 驱动正是利用这些特性将节点和关系转换为 JSON 对象。
由于 JDK 中没有标准的 JSON 对象,此功能需要额外的可选依赖,具体如下面章节所述。
使用 Jackson Databind
Neo4j JDBC 驱动可以使用 Jackson Databind 将图对象转换为 JSON 节点,并将这些对象读取回可在 Cypher 查询中使用的映射。
在您的类路径或模块路径中加入以下依赖,以启用映射到 JsonNode 类型的对象
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.1</version>
</dependency>
现在,您可以将 JsonObject.class 作为类型参数传递给 ResultSet 的任意 getObject 重载(该重载支持类型参数),以如此方式检索 JSON
import java.io.StringWriter;
import java.sql.DriverManager;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class ReadNodesIntoJson {
public static void main(String... args) throws Exception {
var objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN);
try (var connection = DriverManager.getConnection("jdbc:neo4j://:7687/movies", "neo4j", "verysecret");
var stmt = connection.createStatement()) {
var result = stmt.executeQuery("""
MATCH (n:Movie) LIMIT 2
WITH n RETURN collect(n) AS movies
""");
result.next();
var json = result.getObject("movies", JsonNode.class);
var sw = new StringWriter();
objectMapper.writeTree(objectMapper.createGenerator(sw), json);
System.out.println(sw);
}
}
}
输出将类似于如下内容
[ {
"elementId" : "4:5c0c7e77-4034-45a1-ab00-a159be8dbf04:0",
"labels" : [ "Movie" ],
"properties" : {
"title" : "The Matrix",
"tagline" : "Welcome to the Real World",
"released" : 1999
}
}, {
"elementId" : "4:5c0c7e77-4034-45a1-ab00-a159be8dbf04:9",
"labels" : [ "Movie" ],
"properties" : {
"title" : "The Matrix Reloaded",
"tagline" : "Free your mind",
"released" : 2003
}
} ]
您会看到节点包含其 element id、标签列表以及属性对象。此结构与 Query API 对齐,因此任何映射都可以直接使用。JDBC 驱动始终采用“Plain JSON”格式,这样后续映射到领域对象时尽可能简单,无需自定义反序列化器。
下面是一个示例,展示了一个查询将匹配到的所有电影及其演员的结果组织为映射(map),收集为列表,再一次检索为 JSON 节点,最终可以通过 Jackson 的 ObjectMapper 映射为领域对象列表
import java.sql.DriverManager;
import java.util.List;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MapNodesIntoObjects {
public static void main(String... args) throws Exception {
var objectMapper = new ObjectMapper();
record Actor(String name, short born) {
}
record Movie(String title, short released, List<Actor> actors) {
}
try (var connection = DriverManager.getConnection("jdbc:neo4j://:7687/movies", "neo4j",
"verysecret"); var stmt = connection.createStatement()) {
var result = stmt.executeQuery("""
MATCH (m:Movie)<-[:ACTED_IN]-(a:Person)
WITH m, collect(a{.*}) AS actors
ORDER BY m.title
LIMIT 5
RETURN collect({title: m.title, released: m.released, actors: actors})
""");
result.next();
var json = result.getObject(1, JsonNode.class); (1)
var movies = objectMapper.treeToValue(json, new TypeReference<List<Movie>>() {}); (2)
movies.forEach(System.out::println);
}
}
}
| 1 | 首先再次将列表检索为 JSON 数组 |
| 2 | 使用 Jackson 的 ObjectMapper 将该数组映射为包含演员的 Movie 对象列表 |
当然,写回 JSON 节点同样可行
import java.sql.DriverManager;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class WritingObjects {
public static void main(String... args) throws Exception {
var objectMapper = new ObjectMapper();
record Movie(String title, String tagline, long released) {
}
var movie = new Movie("title", "tagline", 2025);
try (var connection = DriverManager.getConnection("jdbc:neo4j://:7687/movies", "neo4j",
"verysecret"); var stmt = connection.prepareStatement("CREATE (m:Movie $1) RETURN m")) {
stmt.setObject(1, objectMapper.valueToTree(movie));
var rs = stmt.executeQuery();
rs.next();
var json = rs.getObject("m", JsonNode.class);
var newMovie = objectMapper.treeToValue(json.get("properties"), Movie.class);
System.out.println("New movie " + newMovie + " has id " + json.get("elementId"));
}
}
}
它将产生类似如下的输出
New movie Movie[title=title, tagline=tagline, released=2025] has id "4:5c0c7e77-4034-45a1-ab00-a159be8dbf04:173"