从 SDN+OGM 迁移到 SDN
以往 SDN+OGM 迁移中的已知问题
SDN+OGM 多年来经历了相当长的发展历程,我们深知迁移大型应用系统既不轻松,也不会立刻带来收益。我们观察到的从旧版本 Spring Data Neo4j 迁移到新版本时遇到的主要问题大致按顺序如下:
跳过了不止一次大版本升级
虽然 Neo4j-OGM 可以独立使用,但 Spring Data Neo4j 不行。它在很大程度上依赖 Spring Data,进而依赖 Spring Framework 本身,这最终会影响您应用的大部分内容。应用的结构——即框架部分渗透到业务代码中的程度——决定了您需要改造应用的多少。如果您的应用中有多个 Spring Data 模块,或者您在与图数据库相同的服务层中访问关系型数据库,情况会更糟。升级两个对象映射框架绝非易事。
依赖通过 Spring Data 本身配置的内嵌数据库
SDN+OGM 项目中的内嵌数据库由 Neo4j-OGM 配置。假设您想从 Neo4j 3.0 升级到 3.5,就必须同时升级整个应用。这是为什么?因为您选择将数据库内嵌到应用中,也就把自己绑定到了配置该内嵌数据库的模块上。要换一个内嵌数据库版本,就必须升级配置它的模块,因为旧模块不支持新数据库。而 Neo4j-OGM 总是对应某个 Spring Data 版本,因此您还得升级 Spring Data。Spring Data 又依赖 Spring Framework,于是第一个要点中所述的问题便同样适用。
不确定应包含哪些构建块
要把这些术语弄清楚并不容易。我们在这里介绍了 SDN+OGM 环境的构建块。情况可能是,所有这些依赖都是不经意间陆续加入的,导致您要面对大量相互冲突的依赖。
基于这些观察,我们建议在从 SDN+OGM 切换到 SDN 之前,先确认您当前的应用仅使用 Bolt 或 http 传输协议。这样,您的应用及其访问层就在很大程度上独立于数据库版本。在此状态下,再考虑从 SDN+OGM 迁移到 SDN。
准备从 SDN+OGM Lovelace 或 SDN+OGM Moore 迁移到 SDN
Lovelace 发布列车对应 SDN 5.1.x 和 OGM 3.1.x,Moore 对应 SDN 5.2.x 和 OGM 3.2.x。
首先,您必须确保应用通过 Bolt 协议以服务器模式连接 Neo4j 运行;以下三种情况中有两种需要进行相应调整:
您使用的是内嵌模式
您在项目中添加了 org.neo4j:neo4j-ogm-embedded-driver 和 org.neo4j:neo4j,并通过 OGM 设施启动数据库。这种方式已不再受支持,您必须搭建一个标准的 Neo4j 服务器(独立模式和集群模式均受支持)。
必须移除上述依赖。
从内嵌方案迁移可能是最艰难的迁移,因为您还需要搭建服务器。但它本身也最有价值:今后您将能够独立升级数据库本身,而无需考虑应用框架和数据访问框架。
您使用的是 HTTP 传输协议
您添加了 org.neo4j:neo4j-ogm-http-driver,并配置了类似 [user:password@localhost:7474](http://user:password@localhost:7474) 的 URL。该依赖必须替换为 org.neo4j:neo4j-ogm-bolt-driver,并且您需要配置类似 bolt://localhost:7687 的 Bolt URL,或使用新的 neo4j:// 方案——它还会处理路由。
您已经在间接使用 Bolt
默认的 SDN+OGM 项目使用 org.neo4j:neo4j-ogm-bolt-driver,因而间接使用纯 Java 驱动。您可以保留现有的 URL。
迁移
一旦确认 SDN+OGM 应用能够按预期通过 Bolt 运行,就可以开始迁移到 SDN。
-
移除所有
org.neo4j:neo4j-ogm-*依赖 -
不再支持通过
org.neo4j.ogm.config.Configurationbean 配置 SDN;驱动的所有配置改为通过新的 Java 驱动起步依赖进行。您尤其需要调整 URL 和认证相关的属性,请参阅新旧属性对比
不能通过 XML 配置 SDN。如果您的 SDN+OGM 应用曾经这样做,请务必了解 Spring 应用的注解驱动配置或函数式配置。如今最简单的选择是 Spring Boot。有了我们的起步依赖,除连接 URL 和认证信息之外,所有必要配置都已为您准备就绪。
# Old
spring.data.neo4j.embedded.enabled=false # No longer supported
spring.data.neo4j.uri=bolt://localhost:7687
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=secret
# New
spring.neo4j.uri=bolt://localhost:7687
spring.neo4j.authentication.username=neo4j
spring.neo4j.authentication.password=secret
当 SDN 和驱动最终完全取代旧配置方式时,这些新属性未来可能还会发生变化。
最后,添加新的依赖;Gradle 和 Maven 的方式请参阅快速上手。
之后就可以替换注解了:
| 旧 | 新 |
|---|---|
org.neo4j.ogm.annotation.NodeEntity | org.springframework.data.neo4j.core.schema.Node |
org.neo4j.ogm.annotation.GeneratedValue | org.springframework.data.neo4j.core.schema.GeneratedValue |
org.neo4j.ogm.annotation.Id | org.springframework.data.neo4j.core.schema.Id |
org.neo4j.ogm.annotation.Property | org.springframework.data.neo4j.core.schema.Property |
org.neo4j.ogm.annotation.Relationship | org.springframework.data.neo4j.core.schema.Relationship |
org.springframework.data.neo4j.annotation.EnableBookmarkManagement | 无替代,不再需要 |
org.springframework.data.neo4j.annotation.UseBookmark | 无替代,不再需要 |
org.springframework.data.neo4j.annotation.QueryResult | 使用投影;不再支持任意结果映射 |
若干 Neo4j-OGM 注解在 SDN 中尚无对应注解,有些也永远不会有。随着我们支持更多特性,上述列表会持续补充。
书签管理
@EnableBookmarkManagement 和 @UseBookmark,以及 org.springframework.data.neo4j.bookmark.BookmarkManager 接口及其唯一实现 org.springframework.data.neo4j.bookmark.CaffeineBookmarkManager 都已移除,不再需要。
SDN 会在所有事务中自动使用书签,无需配置。您可以移除 CaffeineBookmarkManager 的 bean 声明,以及对 com.github.ben-manes.caffeine:caffeine 的依赖。
如果确实有必要,您可以按照这些说明禁用自动书签管理。
约束和索引的自动创建
SDN 5.3 及更早版本提供了来自 Neo4j-OGM 的“自动索引管理器”。
@Index、@CompositeIndex 和 @Required 已被移除,且没有替代方案。为什么?我们认为创建模式——即使是针对无模式数据库——不属于领域建模的一部分。您可能会说 SDN 模型就是模式,但我们的回答是:我们甚至更倾向于命令查询分离,也就是说,我们宁愿分别定义读模型和写模型。它们在编写“常规”写入操作和读取图状结构结果时非常方便。
此外,这些注解或其取值还与特定的 Neo4j 版本或版本号绑定,导致它们难以维护。
然而,最有说服力的理由是上生产环境:虽然所有生成模式的工具在开发期间确实很有帮助——对于强制严格模式的数据库更是如此——但它们在生产环境中往往不那么理想:您如何处理同时运行的不同版本应用?让版本 A 去断言由更新的版本 B 创建的索引?
我们认为更好的做法是提前掌控这一点,并建议使用受控的数据库迁移,基于 Liquigraph 或 Neo4j migrations 这样的工具。后者已在 JHipster 项目中与 SDN 一同使用。这两个项目的共同点是:它们将当前模式版本存储在数据库中,并在更新任何内容之前确保模式与预期一致。
迁移出旧的 Neo4j-OGM 注解会影响 @Index、@CompositeIndex 和 @Required,以下是一个使用 Neo4j-OGM 自动索引管理器的类的示例。
import org.neo4j.ogm.annotation.CompositeIndex;
import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.Index;
import org.neo4j.ogm.annotation.Required;
@CompositeIndex(properties = {"tagline", "released"})
public class Movie {
@Id @GeneratedValue Long id;
@Index(unique = true)
private String title;
private String description;
private String tagline;
@Required
private Integer released;
}
它的注解等价于以下 Cypher 模式(以 Neo4j 4.2 为准):
CREATE CONSTRAINT movies_unique_title ON (m:Movie) ASSERT m.title IS UNIQUE;
CREATE CONSTRAINT movies_released_exists ON (m:Movie) ASSERT EXISTS (m.released);
CREATE INDEX movies_tagline_released_idx FOR (m:Movie) ON (m.tagline, m.released);
使用不带 unique = true 的 @Index 等价于 CREATE INDEX movie_title_index FOR (m:Movie) ON (m.title)。请注意,唯一索引本身已经隐含了普通索引。