概念

本章介绍适用于所有提供模块的各种概念。尤其要查看有关 命名约定 的页面,该页面适用于所有基于 Cypher 和 Java 的迁移和回调。

连接

Neo4j-Migrations 仅使用 Neo4j Java 驱动程序。大多数情况下,您将预先配置的驱动程序对象传递给我们的 API。Spring-Boot-Plugin 依赖于 Spring-Boot 提供的驱动程序实例,该实例可以通过 spring.neo4j.* 命名空间中的属性进行配置。CLI 和 Maven-Plugin 提供参数来定义 URL、用户名和密码。

所有这一切意味着我们可以使本章简短,并且基本上可以参考驱动程序的文档:Neo4j Java 驱动程序手册 v4.4。为了便于使用,以下是驱动程序可能采用的最常见 URL 形式。所有 URL 都具有以下格式:<NEO4J_PROTOCOL>://<HOST>:<PORT>。Neo4j 协议可能是以下之一

URI 方案 路由 描述

neo4j

不安全

neo4j+s

使用完整证书进行安全保护

neo4j+ssc

使用自签名证书进行安全保护

bolt

不安全

bolt+s

使用完整证书进行安全保护

bolt+ssc

使用自签名证书进行安全保护

您无需过分关注驱动程序 API,只需了解如何创建实例即可

清单 1. 创建 Neo4j-Java-Driver 的实例
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Config;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;

class HowToCreateADriverInstance {

	public static void main(String... args) {
		Driver driver = GraphDatabase.driver(
			"neo4j://your.database.io",
			AuthTokens.basic("neo4j", "secret"),
			Config.defaultConfig()
		);
	}
}

如果您未使用我们的集成之一,则需要将此实例传递给 Neo4j-Migrations Core API。大多数其他操作都可以通过 Cypher 脚本单独完成。如果您需要对迁移过程进行更多控制,请查看我们的 基于 Java 的迁移支持

迁移

迁移是您应用于数据库的所有操作或重构。这些操作可能是创建、更改或删除索引和约束,或更改数据。有时您甚至可能想要创建用户或数据库。

基于 Cypher (.cypher)、基于目录 (.xml) 和基于类的 (例如 .java.kt) 迁移需要遵循一定的命名约定才能被识别

V1_2_3__Add_last_name_index.(cypher|xml|java)
  • 前缀 V 代表“Versioned 迁移”或 R 代表“Repeatable 迁移”

  • 版本,可以使用下划线分隔多个部分(可选)

  • 分隔符:__(两个下划线)

  • 必需描述:可以使用下划线或空格分隔单词

  • 后缀:取决于给定的类型。

回调 (见 命名约定) 和一些 扩展(Neo4j-Migrations 支持)除外。

基于 Cypher 的

基于 Cypher 的迁移基本上可以是您能以 Cypher 语句 的形式写下的任何内容。基于 Cypher 的迁移可以包含一个或多个语句,多个行用 ; 分隔,后面跟着换行符。默认情况下,一个脚本中的所有语句将在单个事务中执行。

这是一个示例

清单 2. neo4j/migrations/V007__BondTheNameIsBond.cypher
CREATE (agent:`007`) RETURN agent;
UNWIND RANGE(1,6) AS i
WITH i CREATE (n:OtherAgents {idx: '00' + i})
RETURN n
;

此脚本包含两个不同的语句。

Neo4j-Migrations 默认情况下会在 classpath:neo4j/migrations 中查找所有与 命名约定 中描述的名称匹配的 *.cypher 文件。您可以使用 Core API 或 Spring-Boot-Starter 或 Maven-Plugin 中的相应属性更改(或添加到)此默认值,如下所示

清单 3. 通过 Core API 更改要扫描迁移(和回调)的位置
MigrationsConfig configLookingAtDifferentPlaces = MigrationsConfig.builder()
    .withLocationsToScan(
        "classpath:my/awesome/migrations", (1)
        "file:/path/to/migration" (2)
    ).build();
1 查看类路径上的其他位置
2 查看给定文件系统路径中的其他位置

在 Cypher 脚本中切换数据库

使用 :USE 命令

:USE 命令与 Neo4j-Browser 或 Cypher-Shell 中的含义相同:所有后续命令都将在给定数据库中应用。事务模式将根据每个数据库的配置进行应用,并在您再次切换数据库时“重新开始”。这是执行此类操作的首选方法

清单 4. 使用 :USE 在飞行中切换数据库
CREATE database foo IF NOT EXISTS WAIT;
:use foo;
CREATE (n:InFoo {foo: 'bar'});
:use neo4j;
CREATE (n:InNeo4j);
使用 Cypher 关键字 USE

当然,您可以在脚本中使用 Cypher 关键字 USE <graph>(参见 USE)。不过,要记住以下几点

  • 如果您以创造性的方式将其与 Neo4j-Migrations 自身提供的架构和目标数据库选项结合使用,可能会变得很棘手

  • 如果您每个脚本有多个语句(这完全没问题),并且其中一个语句应该使用 USE,则必须配置 Neo4j-Migrations 以使用 TransactionMode#PER_STATEMENT(参见 事务,意味着将脚本的每个语句在单独的事务中运行。这样做可能会更容易出错,因为如果一个语句失败,那么它很可能会使您的数据库处于不一致状态,因为之前的所有内容都已经提交。

基于目录的

迁移可用于以迭代方式定义本地目录。发现的每个迁移都将对 Migration 实例上下文中已知的目录做出贡献。

基于目录的迁移以 XML 格式编写,每个迁移可以包含一个 <catalog /> 项目和每个迁移的多个 <operation /> 项目。

定义基于目录的迁移的最简单方法如下所示

清单 5. V01__Create_unique_isbn.xml
<?xml version="1.0" encoding="UTF-8"?>
<migration xmlns="https://michael-simons.github.io/neo4j-migrations">
  <create>
    <constraint name="unique_isbn" type="unique">
      <label>Book</label>
      <properties>
        <property>isbn</property>
      </properties>
    </constraint>
  </create>
</migration>

这里为标记为 Book 的所有节点的 isbn 属性定义了一个唯一约束。此约束仅在本地已知,不会对上下文目录做出贡献。

这也可以这样重写

清单 6. V01__Create_unique_isbn.xml
<?xml version="1.0" encoding="UTF-8"?>
<migration xmlns="https://michael-simons.github.io/neo4j-migrations">
  <catalog>
    <constraints>
      <constraint name="unique_isbn" type="unique">
        <label>Book</label>
        <properties>
          <property>isbn</property>
        </properties>
      </constraint>
    </constraints>
  </catalog>
  <create item="unique_isbn"/>
</migration>

此约束也可以在以后重用

清单 7. V23__Drop_old_constraint.xml
<?xml version="1.0" encoding="UTF-8"?>
<migration xmlns="https://michael-simons.github.io/neo4j-migrations">
  <drop item="unique_isbn"/>
</migration>

也支持索引

清单 8. V01__Create_an_index_local.xml
<?xml version="1.0" encoding="UTF-8"?>
<migration xmlns="https://michael-simons.github.io/neo4j-migrations">
  <create>
    <index name="node_index_name">
      <label>Person</label>
      <properties>
        <property>surname</property>
      </properties>
    </index>
  </create>
</migration>
XML 架构也支持索引的类型:FULLTEXTTEXT。前者是众所周知的 Lucene 支持的索引,后者是 Neo4j 中引入的新 TEXT 索引。

要详细了解方案,请查看 XML 方案说明,并确保您也遵循 有关目录的概念 以及 目录示例

最后但并非最不重要的是,Neo4j-Migrations 提供了一些内置的重构,这些重构是根据 APOC 重构 建模的,但不需要在数据库或集群中安装 APOC。

上面 APOC 文档中给出的示例可以用以下目录项完全建模

清单 9. V42__Rename_labels.xml
<?xml version="1.0" encoding="UTF-8"?>
<migration xmlns="https://michael-simons.github.io/neo4j-migrations">
  <refactor type="rename.label">
    <parameters>
      <parameter name="from">Engineer</parameter>
      <parameter name="to">DevRel</parameter>
      <parameter name="customQuery"><![CDATA[
        MATCH (person:Engineer)
        WHERE person.name IN ["Mark", "Jennifer", "Michael"]
        RETURN person
      ]]></parameter>
    </parameters>
  </refactor>
</migration>

它将所有匹配自定义查询的节点的标签 Engineer 重命名为 DevRel

所有受支持的重构都在 重构 中描述。

使用 XML 相比于基于 Cypher 的迁移,这种方法有什么优势? 在过去十年中,用于定义约束和索引的语法发生了很大变化,许多在 Neo4j 3.5 中可行的变体在一段时间内已被弃用,并将消失在 Neo4j 5.0 中。

使用约束和索引的中立表示,我们可以将这些项目转换为适合目标数据库的语法。此外,我们还可以对没有这些项目的旧数据库执行幂等操作。

此外,对于创建诸如重构之类的概念的表示,需要某种结构化形式。

与使用 先决条件 的基于 Cypher 的迁移相比,使用基于目录的迁移来创建针对特定 Neo4j 版本的约束和索引有什么优势? 使用先决条件,您有责任处理新出现的 Neo4j 版本,并确保您获得了正确的语法。使用基于目录的迁移,您将免除这项义务。先决条件在 目录的概念 出现之前就已经存在,可以用于多种目的(例如,确保实际数据存在)。与之相反,基于目录的迁移非常关注实际的架构项目。

但是,基于目录的迁移也支持先决条件。它们可以作为 XML 处理指令添加到文档中的任何位置,并且看起来像这样

清单 10. 先决条件作为处理指令的示例
<?xml version="1.0" encoding="UTF-8"?>
<migration xmlns="https://michael-simons.github.io/neo4j-migrations">
  <?assert that edition is enterprise ?>
  <?assume q' RETURN true?>
</migration>

它们可以出现在文档中的任何位置,但建议将其放在根元素中。

虽然 constraintindex 这两个元素都支持名为 options 的子元素,但这些元素尚未被渲染或使用。

基于 Java 的

Neo4j-Migrations 提供了 ac.simons.neo4j.migrations.core.JavaBasedMigration 接口供您实现。根据此接口,您可以做的不仅仅是通过添加或更改数据来迁移事物:您可以以编程方式重构数据库中的所有内容。一个可能的迁移看起来像这样

清单 11. 基于 Java 的重构示例
package some.migrations;

import ac.simons.neo4j.migrations.core.JavaBasedMigration;
import ac.simons.neo4j.migrations.core.MigrationContext;

import org.neo4j.driver.Driver;
import org.neo4j.driver.Session;

public class V001__MyFirstMigration implements JavaBasedMigration {

    @Override
    public void apply(MigrationContext context) {
        try (Session session = context.getSession()) { (1)
            // Steps necessary for a migration
        }
    }
}
1 MigrationContext 提供 getSession()getSessionConfig() 来与 getDriver() 结合使用。当您想要访问响应式或异步会话时,后者很有用。重要的是,您使用便捷方法 getSession() 或使用提供的配置创建会话,因为只有这样才能保证您的数据库会话连接到配置的目标数据库,并使用配置的用户。此外,我们的上下文将负责管理 Neo4j 因果集群书签。但是,如果您觉得有必要切换到其他数据库,您可以随意使用驱动程序实例。在基于 Java 的迁移中,事务处理完全由您负责。

您无需以任何方式对基于 Java 的迁移进行注释。Neo4j-Migrations 会在类路径中找到它们。与 Cypher 脚本相同的命名要求也适用于基于 Java 的迁移,请参阅 命名约定

在 GraalVM 原生镜像上运行 Neo4j-Migrations 时,存在一些限制:您可能能够或不能说服运行时在原生镜像中找到接口的实现。您至少必须将这些类显式包含在原生镜像中,除非它们以其他方式使用。
CLI 将直接拒绝以其原生形式(使用 --package 选项时)扫描基于 Java 的迁移。它只在 JVM 模式下支持它们。

虽然从理论上讲,您也可以扩展公共基接口 Migration,但我们不建议这样做。事实上,在 JDK 17 上,我们禁止这样做。请仅使用 JavaBasedMigration 作为您的程序化迁移的基接口。

回调

回调是重构或迁移链的一部分,该链位于事物的链之外。因此,这些回调可用于确保在任何其他操作发生之前,某些数据、结构或其他先决条件可用或已满足。它们在集成测试中也很有用。您可能希望将您的迁移作为应用程序主源代码树的一部分,并且同时在测试源代码树中拥有相同的文件夹,其中包含许多回调,例如,在 afterMigrate 事件中创建测试数据。

回调在被调用后不被认为是不可变的,它们的调用也不会存储在历史图中。这为您提供了一个钩子,可以将一些更易失性的内容添加到您的重构中。

beforeFirstUse 回调在您想要在应用迁移之前创建目标数据库的情况下特别有用:它将始终在连接用户的 home 数据库中调用,因此,此时目标数据库不需要存在。

请注意,为了使此方法起作用,您**必须**指定**目标**和**模式**数据库:模式数据库必须存在,不能使用 beforeFirstUse 回调创建。这是因为迁移始终在由一对节点表示的锁中运行。
相应的 CLI 调用将如下所示
neo4j-migrations --schema-database neo4j --database canBeCreatedWithCallback apply
相应的回调将包含
CREATE DATABASE canBeCreatedWithCallback IF NOT EXISTS;

生命周期阶段

支持以下阶段

beforeFirstUse

唯一一个仅对 Neo4j-Migrations 的任何给定实例运行一次的阶段。它将在第一个连接打开时,在调用任何其他操作之前运行。此阶段中的回调将始终在模式数据库中调用,而不是在目标数据库中调用,因此它们不需要目标数据库存在。此外,不会执行用户模拟。这可用于在运行任何迁移或验证之前创建目标数据库。

beforeMigrate

在迁移数据库之前。

afterMigrate

在迁移数据库之后,与结果无关。

beforeClean

在清理数据库之前。

afterClean

在清理数据库之后,与结果无关。

beforeValidate

在验证数据库之前。

afterValidate

在验证数据库之后,与结果无关。

beforeInfo

在获取有关目标数据库的信息之前。

afterInfo

在获取有关目标数据库的信息之后。

使用项目目录

Neo4j 是一个无模式或具有少量模式的数据库。节点有标签,关系有类型,两者都可以具有属性。因此,属性图。但是,没有“硬”模式来确定所有节点都具有相同类型的所有相同属性。

但是,有一些概念可以强制在实体上存在属性:约束。约束还可以强制执行唯一性和键;它们与索引密切相关。约束和索引是我们所说的 Neo4j-Migrations 中的模式。

为什么是 XML?虽然 XML 一直被贬低,但它比 JSON 和 YAML 具有几个优点,特别是在模式方面:有许多选项可以验证给定的文档,文档类型定义 (DTD) 和 XML 模式是其中两个。Neo4j-Migrations 选择了后者,它在 附录 中有记录。您的大多数工具都应该能够加载此文档并验证任何迁移,并指导您了解什么可行,什么不可行。
我们的优势在于,XML 支持直接与 JVM 捆绑在一起,我们无需引入任何额外的依赖项来解析和验证内容。

目录还用于表示预定义或内置的 重构,例如重命名类型的全部出现或标签。

什么是目录?

在 Neo4j-Migrations 中,引入了目录的概念。目录包含与模式相同的类型实体,迁移可以从目录中获取元素来定义最终模式。

项目可以在目录中多次存在,由它们的 id 和定义它们的迁移版本标识。这样,例如,删除操作可以引用应用于模式的实体的最后一个版本,而不是最新的版本,在最新的版本中,属性或选项可能已经更改。

重构作为目录中的一个通用概念存在,它们不需要定义,而只需声明为要执行的 操作 即可。

目录是如何定义的?

目录有两种形式,远程目录和本地目录。远程目录(换句话说,由数据库模式定义的目录)更容易理解:它是对 Neo4j-Migrations 支持的数据库模式中包含的所有项目的只读视图,例如约束和索引。它可以随时按需检索。

本地目录稍微复杂一些:它是在发现迁移时以迭代方式构建的。基于目录的迁移按版本排序读取。它们在 <catalog /> 定义中的项目需要在每个迁移中具有唯一的 id(名称)。所有项目都以版本化的方式添加到本地目录中。如果名为 a 的项目在版本 nn+x 中都被定义,则它将在两个变体中都可以在目录中访问。因此,Neo4j-Migrations 例如可以支持删除未命名的项目,并以新的方式重新创建它们。版本化的本地目录方法还允许执行高级操作,如 verify:在迁移 n+1 中触发的针对本地目录的远程目录验证可以参考版本 n 中的本地目录(默认值)以断言所有后续操作的基础,或者参考当前版本以确保在不执行更多操作的情况下,所有内容都存在于给定时间点。

最后但并非最不重要的一点是:有时需要在给定迁移中从头开始。为此,目录元素支持一个额外的属性 reset。在任何给定迁移中将此属性设置为 true 将导致目录在该版本中重置。重置意味着用空目录替换(<catalog reset="true" />)或用实际内容替换。

与目录一起使用的操作

目录基于迁移可用的操作是

create

创建项目

drop

删除项目

verify

将本地定义的目录与远程模式进行验证

apply

从远程模式中删除所有支持的类型,并创建本地目录中的所有元素。

refactor

执行几个预定义的重构中的一个

虽然 createdrop 对单个项目起作用,但 verifyapply 对定义的版本范围内的整个已知目录起作用。

关于命名的一句话:Neo4j-Migrations 要求目录项在整个目录中具有唯一的名称。与 Neo4j 数据库本身不同,不允许对约束和索引都使用名称 wurstsalat。在这种情况下,推荐的名称为 wurstsalat_existswurstsalat_index

createdrop 操作默认情况下是幂等的。可以使用 ifNotExistsifExists 属性(其值为 false)来更改此行为。

请注意,幂等并不意味着“强制”,尤其是在 create 情况下。如果您想更新/替换现有的约束,并且不确定它是否存在,请使用

<migration xmlns="https://michael-simons.github.io/neo4j-migrations">
    <drop item="a" ifExists="true" />
    <create item="a" />
</migration>

删除操作将确保约束消失,而创建操作将安全地构建一个新的约束。

验证(或断言)

verify 断言目录中的所有项目都以等效或相同形式存在于数据库中。这是迁移中一个有用的步骤,可以确保在应用更多迁移之前,一切“按预期”。因此,它只能在运行任何 createdropapply 命令之前使用。

默认情况下,要进行验证的目录项目由出现在 verify 中的迁移之前的所有先前版本组成。例如,在迁移 V2.1 中,出现了 verify。来自版本 1.0 到 2.0 的所有目录项都将参与断言。在 2.1 中使用相同名称定义的项目将不会被断言,因此您可以断言给定状态,然后重新定义其中的部分。可以通过使用属性 latest 来更改此行为,在元素上将其设置为 true<verify latest="true" />)。这将采用版本中定义的目录。

应用整个目录

apply 另一方面,会删除当前物理模式中的所有项目,并在迁移的当前版本处创建目录中的所有项目状态。从上面的相同示例中,来自 1.0 到 2.1 的所有内容(包括 2.1)都将被包括在内,定义将分别由它们的名称或 id 识别。

apply 操作从数据库加载所有支持的项目类型,删除它们,然后创建本地目录中的所有项目。这是一个可能具有破坏性的操作,因为它可能会删除您没有替代方案的项目。
另外请注意,neo4j-migrations 永远不会删除锁定节点正常运行所需的约束(基本上,为标签 __Neo4jMigrationsLock 定义的任何约束)。

apply 不能与同一个迁移中的 dropcreate 一起使用。

执行重构

refactor 用于运行参数化的预定义重构。refactor 元素可以在 verify 操作之后使用,也可以在 dropcreate 操作之前、之后或之间使用。它将按照定义的顺序执行。它不能与 apply 一起使用。请查看一般 目录示例附录,以获取执行预定义重构的一些具体示例。

从实际数据库模式创建目录

API 提供了 getDatabaseCataloggetLocalCatalog 方法。前者读取 Neo4j 模式中所有支持的项,并根据它们创建目录视图;后者提供对所有迁移定义的目录的访问。

CLI 使用这些方法来提供将整个数据库模式作为目录定义以我们自己的 XML 格式或作为针对特定 Neo4j 版本的 Cypher 脚本进行转储的能力。

最后但并非最不重要的是,还有一个公共 API ac.simons.neo4j.migrations.core.catalog.CatalogDiff.between 可用于比较两个目录,并评估它们是相同的、等效的还是不同的。

无法从现有数据库中推导出重构。

命名约定

基于 Cypher 的资源

所有基于 Cypher 的资源(尤其是迁移和回调脚本)都需要 .cypher 作为扩展名。Core API、Spring-Boot-Starter 和 Maven-Plugin 默认情况下将在 classpath:neo4j/migrations 中搜索此类 Cypher 脚本。CLI 没有默认搜索位置。

迁移脚本

基于 Cypher 脚本的迁移必须具有遵循给定模式的名称才能被识别

V1_2_3__Add_last_name_index.cypher
  • 前缀 V 代表“Versioned 迁移”或 R 代表“Repeatable 迁移”

  • 版本,可以使用下划线分隔多个部分(可选)

  • 分隔符:__(两个下划线)

  • 必需描述:可以使用下划线或空格分隔单词

  • 后缀:.cypher

这适用于应用程序外部(在文件系统中)和应用程序内部(作为资源)的 Cypher 脚本。

基于 Cypher 的迁移脚本被认为是不可变的,一旦应用就无法更改。我们计算它们的校验和并在模式数据库中记录它。如果您在应用基于 Cypher 的迁移后更改了它,则任何进一步的应用都将失败。通过将迁移标记为可重复,您表示每当其校验和更改时,重复迁移都是安全的。

回调脚本

如果 Cypher 脚本符合以下模式,则它将被识别为给定生命周期的回调

nameOfTheLifecyclePhase.cypher
nameOfTheLifecyclePhase__optional_description.cypher

nameOfTheLifecyclePhase 必须与支持的生命周期阶段之一的名称完全匹配(区分大小写),后跟一个可选的描述和后缀 .cypher,并通过两个下划线 (__) 与阶段名称分隔。该描述用于对同一生命周期阶段的不同回调脚本进行排序。如果您在同一生命周期阶段使用多个脚本但没有描述,则顺序是不确定的。

回调脚本不被认为是不可变的,可以在执行之间更改。如果您在其中使用 DDL 语句(例如 CREATE USERCREATE DATABASE),请确保在所需子句中查找 IF NOT EXITS 选项,以便这些语句变得幂等。

基于目录的迁移

基于目录的迁移(参见 使用项目目录)是基于 migration.xsd 方案的 XML 文件。因此,它们需要扩展名 .xml,否则遵循与 基于 Cypher 的资源 相同的命名约定。

基于 Java 的迁移

对于基于 Java(或实际上可以编译为有效 Java 类)的迁移,除了扩展名外,与 基于 Cypher 的脚本 采用相同的命名约定。为了坚持上述示例,V1_2_3__Add_last_name_index.cypher 变为 V1_2_3__Add_last_name_index 作为简单的类名,或者以源代码形式,V1_2_3__Add_last_name_index.java

我们建议使用类似于以下内容

public class V1_2_3__AddLastNameIndex implements JavaBasedMigration {
    @Override
    public void apply(MigrationContext context) {
        // Your thing
    }

    @Override
    public String getSource() {
        return "Add last name index"; (1)
    }
}
1 默认情况下,简单类名将被添加到历史链中。

已应用迁移的链

应用于目标数据库的所有迁移都存储在模式数据库中。目标数据库和模式数据库可以是同一个数据库。如果您是管理用于相同应用程序的不同租户的不同数据库的企业客户,那么使用单独的模式数据库来存储与 Neo4j-Migrations 相关的所有数据绝对有意义。

子图将如下所示

chain of migrations

如果您为任何名称与默认名称 (neo4j) 不同的数据库使用模式数据库,则标记为 __Neo4jMigration 的节点将具有一个名为 migrationTarget 的附加属性,其中包含目标图。

已应用迁移的链是稳定的,当然可以查询它(例如在 回调 中),但您不应该以任何方式或形式修改它。如果您想摆脱它,请使用 clean 操作。

单独的模式数据库

从 1.1.0 版本开始,您可以使用不同的数据库来存储有关迁移的信息。您需要运行 Neo4j 4+ 企业版。命令行参数和属性分别是 schema-database,贯穿整个配置。给定的名称必须是有效的 Neo4j 数据库名称(参见 管理和配置)。数据库必须存在,并且用户必须对它具有写访问权限。

有效场景为

  • 将模式数据库用于另一个数据库

  • 将模式数据库用于维护不同数据库的多个迁移

  • 使用模式数据库和目标数据库对

Neo4j-Migrations 将在模式数据库中创建子图,这些子图可以通过 __Neo4jMigration 节点中的 migrationTarget 属性识别。Neo4j-Migrations 不会为默认数据库 (通常为 neo4j) 记录 migrationTarget,因此此功能不会破坏与 1.1.0 之前创建的模式的兼容性。

通常最好将管理数据(在本例中为已应用迁移的链)与您自己的数据分离,无论后者是由重构本身还是应用程序创建或更改的。因此,我们建议您在使用企业版时使用分离的数据库。

事务

Neo4j-Migrations 直接管理的所有操作(除了基于目录的迁移)都在事务函数内执行。这本质上是一个围绕一个或多个语句的范围,这些语句将在某些条件下(例如,在集群设置中失去连接时)重试。

您可以配置一个 基于 Cypher 的 迁移的所有语句是否都进入一个事务函数,或者每个语句是否都进入它自己的事务范围

清单 12. 选择事务行为
MigrationsConfig configPerMigration = MigrationsConfig.builder()
    .withTransactionMode(MigrationsConfig.TransactionMode.PER_MIGRATION)
    .build();

// OR

MigrationsConfig configPerStatement = MigrationsConfig.builder()
    .withTransactionMode(MigrationsConfig.TransactionMode.PER_STATEMENT)
    .build();

Per Migration 是默认设置,因为我们认为它更安全:要么整个迁移应用(或失败),要么没有应用。但是,某些场景需要每个语句一个事务,例如,大多数 DDL 操作(例如创建数据库)可能无法与同一事务中的 DML 操作一起运行。

基于目录的迁移(即通过专用 Neo4j-Migrations API 创建索引和约束)始终在自动提交事务内执行,因为底层连接存在一些缺陷,这些缺陷不允许在创建模式项期间可能发生的某些故障条件下重试或继续使用事务。

先决条件

我们的 基于 Cypher 的迁移 支持一组简单的断言和假设作为执行之前的先决条件。

可以在脚本中以单行 Cypher 注释的形式添加先决条件。一个脚本中的多个先决条件必须全部满足(逻辑上用 AND 连接)。

断言

// assert 开头的先决条件是硬性要求。如果目标数据库无法满足它们,Neo4j-Migrations 将中止。

假设

// assume 开头的先决条件是软性要求。如果无法满足它们,则相应的脚本将被跳过,并且不会成为任何链的一部分。

如果您认为先决条件可能会改变(例如,当请求特定版本时):请确保您具有具有相同文件名的备用脚本,这两个脚本都具有满足匹配情况的先决条件。我们将把它们视为备选方案,并确保更改的校验和不会被视为错误。例如,如果您的迁移突然满足了它以前没有满足的先决条件,因此更改了已应用迁移的链,就会发生这种情况。

需要特定版本

可以使用以下任一方法要求 Neo4j 版本

// assume that edition is enterprise

// assume that edition is community.

需要特定版本

可以使用以下方法要求 Neo4j 版本

// assume that version is 4.3

可以在 is 后面枚举多个版本,并用 , 分隔。

可以使用 lt(小于)或 ge(大于或等于)要求版本范围,例如

// assume that version is ge 4.0

结合使用这两个假设可以安全地使用版本假设(见 上面的警告)。我们建议为支持的最低版本使用一个重构,为支持该功能的所有更高版本使用另一个重构。例如:您的最低支持数据库版本是 4.3,您想创建一个存在约束。您想进行 2 次迁移

清单 13. 43/V0001__Create_existence_constraint.cypher
// assert that edition is enterprise
// assume that version is 4.3
CREATE CONSTRAINT isbn_exists IF NOT EXISTS ON (book:Library) ASSERT exists(book.isbn);

另一个用于 4.4 或更高版本的重构

清单 14. 44/V0001__Create_existence_constraint.cypher
// assert that edition is enterprise
// assume that version is ge 4.4
CREATE CONSTRAINT isbn_exists IF NOT EXISTS FOR (book:Library) REQUIRE book.isbn IS NOT NULL;

前者将仅应用于 4.3,后者将应用于 4.4 或更高版本。如果您的用户在某个时候升级了他们的数据库,Neo4j-Migrations 将识别到它使用了与它兼容的旧脚本,并且不会失败,即使新脚本具有不同的校验和。

基于 Cypher 查询的先决条件

您可以通过以下方法要求基于必须返回单个 boolean 值的查询的先决条件

// assume q' RETURN true

上述情况当然将始终满足。

以下是一个完整的示例

// assert that edition is enterprise
// assert that version is 4.4
// assume q' MATCH (book:Library) RETURN count(book) = 0
CREATE CONSTRAINT isbn_exists IF NOT EXISTS FOR (book:Library) REQUIRE book.isbn IS NOT NULL;

此重构仅在 Neo4j 4.4 企业版(由于存在约束的要求以及使用 4.4 语法)上执行,并且在已经存在标记为 Library 的节点时将被忽略。

为什么只有脚本的先决条件?

由于我们提供了对迁移以及具有有关 Neo4j 版本、版本和对目标数据库和模式数据库的访问的信息的上下文进行 完全编程访问,因此如果我们剥夺了您的决定权,那就是重复工作。在编程重构中,您可以完全自由地在给定的上下文中不做任何事情。迁移将被认真记录下来。

升级旧数据库

鉴于您的应用程序需要支持 Neo4j 的多个版本,包括在您最初创建应用程序时不存在的版本,并且您现在可能在可能已经应用的迁移中包含无效的 Cypher,您可以执行以下操作

  • 在您的迁移位置创建子文件夹或配置其他位置

  • 复制包含在较新 Neo4j 版本中存在问题的 Cypher 的迁移

  • 保持迁移的名称相同,并在这些文件夹中相应地分发它们

  • 向其中一个添加仅匹配 Neo4j 的旧版本的先决条件,并保留其他先决条件不变

  • 调整另一个仅包含“良好”语法的迁移,并为较新的 Neo4j 版本添加先决条件

因此,您支持以下场景

  • 在您的应用程序已经运行的旧数据库版本上,不会有任何变化;具有修复语法 的迁移将被跳过

  • 旧数据库版本的干净状态也是如此

  • 在较新的数据库版本上,将仅应用具有修复语法的迁移。

© . All rights reserved.