附录

词汇表

待处理迁移

参见“已解决迁移”。

已解决迁移

已在类路径或文件系统中解决但尚未应用的迁移。

模式数据库

Neo4j 企业实例或集群中的一个数据库,用于存储来自 Neo4j-Migrations 的模式信息。

目标数据库

Neo4j 企业实例或集群中的一个数据库,由 Neo4j-Migrations 重构。

XML 模式

migration.xsd

在深入了解XML 模式的纯粹乐趣之前,让我们用简单的英语阅读我们的模式可以做什么

  • 一个<migration />可以有零个或正好一个<catalog />元素。

  • 一个<catalog />由零个或一个<constraints />和零个或一个<indexes />元素组成。此外,它可以指示一个reset属性,用当前正在定义的目录替换当前已知的内容。

  • 它们都可以根据其定义包含零个或多个其各自的元素。

  • 一个<migration />可以有零个或一个<verify />操作,并且<verify />操作必须是第一个操作。

  • 然后,一个<migration />可以有零个或多个<create /><drop />操作正好一个<apply />操作。<apply />操作与所有对单个项目进行操作的操作互斥。

  • 对单个项目进行操作(创建和删除)的操作允许在本地定义单个项目。此项目将不参与全局目录。

  • 对单个项目进行操作的操作可以通过使用属性item(一个自由格式字符串)或ref(一个xs:IDREF)来引用此项目。虽然后者对于引用在同一迁移中定义的项目很有用(它通常会由您的工具进行验证),但前者对于引用在其他迁移中定义的项目非常方便。

目录项将要么有一个子元素<label />,在这种情况下它将始终引用节点,要么有一个互斥的子元素<type />,在这种情况下它将始终引用关系。type属性与目标实体无关。此属性定义元素的类型(例如唯一约束或存在约束)。

我们支持以下处理指令

  • <?assert 后跟有效的先决条件 ?>

  • <?assume 后跟有效的先决条件 ?>

查找有效的先决条件此处。用于基于目录的迁移的完整 XML 模式如下所示

清单 1. migration.xsd
<?xml version="1.0" encoding="UTF-8" ?>
<!--

    Copyright 2020-2023 the original author or authors.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

         https://apache.ac.cn/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
       targetNamespace="https://michael-simons.github.io/neo4j-migrations"
       xmlns="https://michael-simons.github.io/neo4j-migrations"
       elementFormDefault="qualified">

  <xs:element name="migration" type="migration"/>

  <xs:complexType name="migration">
    <xs:sequence>
      <xs:element name="catalog" minOccurs="0" type="catalog"/>
      <xs:element name="verify" minOccurs="0" type="verifyOperation" />
      <xs:choice>
        <xs:choice maxOccurs="unbounded">
          <xs:element name="refactor" minOccurs="0" maxOccurs="unbounded" type="refactoring"/>
          <xs:choice maxOccurs="unbounded">
            <xs:element name="create" minOccurs="0" maxOccurs="unbounded" type="createOperation"/>
            <xs:element name="drop" minOccurs="0" maxOccurs="unbounded" type="dropOperation"/>
          </xs:choice>
        </xs:choice>
        <xs:element name="apply" minOccurs="0" type="applyOperation"/>
      </xs:choice>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="refactoring">
    <xs:sequence minOccurs="0">
      <xs:element name="parameters">
        <xs:complexType>
          <xs:sequence maxOccurs="unbounded">
            <xs:any processContents="lax"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
    <xs:attribute name="type">
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:enumeration value="merge.nodes"/>
          <xs:enumeration value="migrate.createFutureIndexes"/>
          <xs:enumeration value="migrate.replaceBTreeIndexes"/>
          <xs:enumeration value="normalize.asBoolean"/>
          <xs:enumeration value="rename.label"/>
          <xs:enumeration value="rename.type"/>
          <xs:enumeration value="rename.nodeProperty"/>
          <xs:enumeration value="rename.relationshipProperty"/>
          <xs:enumeration value="addSurrogateKeyTo.nodes"/>
          <xs:enumeration value="addSurrogateKeyTo.relationships"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:attribute>
  </xs:complexType>

  <xs:complexType name="catalog">
    <xs:all>
      <xs:element name="constraints" minOccurs="0">
        <xs:complexType>
          <xs:sequence>
            <xs:element type="constraint" name="constraint"
                  maxOccurs="unbounded" minOccurs="0"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="indexes" minOccurs="0">
        <xs:complexType>
          <xs:sequence>
            <xs:element type="index" name="index"
                  maxOccurs="unbounded" minOccurs="0"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:all>
    <xs:attribute name="reset" type="xs:boolean" default="false"/>
  </xs:complexType>

  <xs:complexType name="operation" />

  <xs:complexType name="applyOperation">
    <xs:complexContent>
      <xs:extension base="operation" />
    </xs:complexContent>
  </xs:complexType>

  <xs:complexType name="verifyOperation">
    <xs:complexContent>
      <xs:extension base="operation" >
        <xs:attribute name="useCurrent" type="xs:boolean" default="false"/>
        <xs:attribute name="allowEquivalent" type="xs:boolean" default="true"/>
        <xs:attribute name="includeOptions" type="xs:boolean" default="false"/>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>

  <xs:complexType name="itemOperation">
    <xs:complexContent>
      <xs:extension base="operation">
        <xs:sequence>
          <xs:choice minOccurs="0">
            <xs:element name="constraint" type="constraint"/>
            <xs:element name="index" type="index"/>
          </xs:choice>
        </xs:sequence>
        <xs:attribute name="item" type="xs:string"/>
        <xs:attribute name="ref" type="xs:IDREF"/>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>

  <xs:complexType name="createOperation">
    <xs:complexContent>
      <xs:extension base="itemOperation">
        <xs:attribute name="ifNotExists" type="xs:boolean" default="true"/>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>

  <xs:complexType name="dropOperation">
    <xs:complexContent>
      <xs:extension base="itemOperation">
        <xs:attribute name="ifExists" type="xs:boolean" default="true"/>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>

  <xs:complexType name="properties">
    <xs:sequence>
      <xs:element type="xs:string" name="property" maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="catalogItem">
    <xs:attribute name="name" use="required" type="xs:ID"/>
  </xs:complexType>

  <xs:complexType name="constraint">
    <xs:complexContent>
      <xs:extension base="catalogItem">
        <xs:sequence>
          <xs:choice>
            <xs:element name="label" type="xs:string"/>
            <xs:element name="type" type="xs:string"/>
          </xs:choice>
          <xs:element type="properties" name="properties"/>
          <xs:element type="xs:string" name="options" minOccurs="0"/>
        </xs:sequence>
        <xs:attribute name="type" use="required">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="unique"/>
              <xs:enumeration value="exists"/>
              <xs:enumeration value="key"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:attribute>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>

  <xs:complexType name="index">
    <xs:complexContent>
      <xs:extension base="catalogItem">
        <xs:sequence>
          <xs:choice>
            <xs:element name="label" type="xs:string"/>
            <xs:element name="type" type="xs:string"/>
          </xs:choice>
          <xs:element type="properties" name="properties"/>
          <xs:element type="xs:string" name="options" minOccurs="0"/>
        </xs:sequence>
        <xs:attribute name="type">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="property" />
              <xs:enumeration value="fulltext"/>
              <xs:enumeration value="text"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:attribute>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
</xs:schema>

重构

Neo4j-Migrations 包含一组现成的数据库重构。这些重构都非常接近于APOC中提供的那些,但没有任何一个需要在您的数据库中安装 APOC。这些重构主要设计用于从目录中工作,但它们本身也能很好地工作。虽然它们是核心 API的一部分,但它们不依赖于 Migration 实例。它们的 API 遵循与 Neo4j-Migrations 其余部分相同的版本控制保证。重构可能会在以后的某个时间点演变为其模块。

某些重构需要特定的 Neo4j 版本。如果您确实支持多个 Neo4j 版本,请将这些重构定义为单项迁移并添加如下示例中的假设

清单 2. 在运行 Neo4j 4.1+ 时规范化布尔属性
<?xml version="1.0" encoding="UTF-8"?>
<migration xmlns="https://michael-simons.github.io/neo4j-migrations">

  <?assume that version is ge 4.1 ?>

  <refactor type="normalize.asBoolean">
    <parameters>
      <parameter name="property">watched</parameter>
      <parameter name="trueValues">
        <value>y</value>
        <value>YES</value>
      </parameter>
      <parameter name="falseValues">
        <value>n</value>
        <value>NO</value>
      </parameter>
    </parameters>
  </refactor>
</migration>

以编程方式应用重构

虽然您通常会使用从 XML/基于目录的迁移中应用重构的声明性方法,但 Neo4j-Migrations 也为此提供了一个 API

清单 3. 以编程方式重命名一种类型并将属性规范化为布尔值
try (Session session = driver.session()) {
  session.run("CREATE (m:Person {name:'Michael'}) -[:LIKES]-> (n:Person {name:'Tina', klug:'ja'})"); (1)
}

Migrations migrations = new Migrations(MigrationsConfig.defaultConfig(), driver); (2)

Counters counters = migrations.apply(
  Rename.type("LIKES", "MAG"), (3)
  Normalize.asBoolean("klug", List.of("ja"), List.of("nein"))
);

try (Session session = driver.session()) {
  long cnt = session
    .run("MATCH (m:Person {name:'Michael'}) -[:MAG]-> (n:Person {name:'Tina', klug: true}) RETURN count(m)")
    .single().get(0).asLong();
  assert cnt == 1
}
1 将要重构的图形
2 您可以像此处显示的那样创建实例,或者在您已经使用 Spring Boot 启动器或 Quarkus 扩展时使用现有的实例
3 构建尽可能多的重构,它们将按顺序应用。您可以使用计数器检查修改次数

合并节点

Merge.nodes(String source, List<PropertyMergePolicy> mergePolicies)将所有节点、其属性和关系合并到单个节点(匹配节点列表中的第一个节点)。重要的是您的查询使用有序返回才能使其正常工作。

Merge重构需要 Neo4j 4.4+。

作为目录项

<refactor type="merge.nodes">
  <parameters>
    <parameter name="sourceQuery">MATCH (p:Person) RETURN p ORDER BY p.name ASC</parameter>
    <!-- Repeat as often as necessary -->
    <parameter name="mergePolicy">
      <pattern>name</pattern>
      <strategy>KEEP_LAST</strategy>
    </parameter>
    <parameter name="mergePolicy">
      <pattern>.*</pattern>
      <strategy>KEEP_FIRST</strategy>
    </parameter>
  </parameters>
</refactor>

规范化

规范化是获取大量属性和其他图项并对其应用方案的过程。规范化重构至少需要 Neo4j 4.1,使用批处理运行需要 Neo4j 4.4 或更高版本。

将属性规范化为布尔值

通常数据库方案会随着时间的推移而发展,您会发现具有布尔含义和字符串数据类型的属性,其内容例如jaHiddenByesNO或文字 null。为了在查询中正确使用它们,您可能希望将它们规范化为真正的布尔值。这是通过Normalize.asBoolean完成的。

Normalize.asBoolean接收属性的名称以及被视为true的值列表和被视为false的值列表。具有不在任何这些列表中的值的属性将被删除。null作为值是非存在的属性。但是,如果任一列表包含文字null,则将创建具有相应值的属性。

默认情况下,将规范化所有节点和关系的所有属性。要仅对子集应用此重构,即仅对节点应用,您需要使用自定义查询。

Java 示例如下所示

Normalize.asBoolean(
    "watched",
    List.of("y", "YES", "JA"),
	// List.of does not support literal null,
	// so we need to this the old-school
    Arrays.asList("n", "NO", null)
);

与目录项相同

<refactor type="normalize.asBoolean">
  <parameters>
    <parameter name="property">watched</parameter>
    <parameter name="trueValues">
      <value>y</value>
      <value>YES</value>
      <value>JA</value>
    </parameter>
    <parameter name="falseValues">
      <value>n</value>
      <value>NO</value>
      <value />
    </parameter>
    <!-- Optional custom query and batch size -->
    <!--
    <parameter name="customQuery">MATCH (n:Movie) return n</parameter>
    <parameter name="batchSize">42</parameter>
    -->
  </parameters>
</refactor>

重命名标签、类型和属性

ac.simons.neo4j.migrations.core.refactorings.Rename重命名标签、类型和属性,在其默认形式下仅需要 Neo4j 3.5 即可工作。用于筛选目标实体的自定义查询需要 Neo4j 4.1,批处理需要 Neo4j 4.4。

常用方法

inBatchesOf

启用或禁用批处理,需要 Neo4j 4.4

withCustomQuery

提供一个匹配实体(节点或标签)以进行重命名的自定义查询。查询必须返回零行或多行,每行包含一个项目。此功能需要 Neo4j 4.1

重命名标签

Rename.label(String from, String to)重命名所有节点上等于from的值的所有标签为to的值。

作为目录项

<refactor type="rename.label">
  <parameters>
    <parameter name="from">Engineer</parameter>
    <parameter name="to">DevRel</parameter>
    <!-- Optional custom query -->
    <!--
    <parameter name="customQuery"><![CDATA[
      MATCH (person:Engineer)
      WHERE person.name IN ["Mark", "Jennifer", "Michael"]
      RETURN person
    ]]></parameter>
    -->
    <!-- Optional batch size (requires Neo4j 4.4+) -->
    <!--
    <parameter name="batchSize">23</parameter>
    -->
  </parameters>
</refactor>

重命名类型

Rename.type(String from, String to)重命名所有关系上等于from的值的所有类型为to的值。

作为目录项

<refactor type="rename.type">
  <parameters>
    <parameter name="from">COLLEAGUES</parameter>
    <parameter name="to">FROLLEAGUES</parameter>
    <!-- Optional custom query -->
    <!--
    <parameter name="customQuery"><![CDATA[
      MATCH (:Engineer {name: "Jim"})-[rel]->(:Engineer {name: "Alistair"})
      RETURN rel
    ]]></parameter>
    -->
    <!-- Optional batch size (requires Neo4j 4.4+) -->
    <!--
    <parameter name="batchSize">23</parameter>
    -->
  </parameters>
</refactor>

重命名节点属性

Rename.nodeProperty(String from, String to)重命名所有节点上等于from的值的所有属性为to的值。

作为目录项

<refactor type="rename.nodeProperty">
  <parameters>
    <parameter name="from">released</parameter>
    <parameter name="to">veröffentlicht im Jahr</parameter>
    <!-- Optional custom query -->
    <!--
    <parameter name="customQuery"><![CDATA[
      MATCH (n:Movie) WHERE n.title =~ '.*Matrix.*' RETURN n
    ]]></parameter>
    -->
    <!-- Optional batch size (requires Neo4j 4.4+) -->
    <!--
    <parameter name="batchSize">23</parameter>
    -->
  </parameters>
</refactor>

重命名类型属性

Rename.typeProperty(String from, String to)重命名所有关系上等于from的值的所有属性为to的值。

作为目录项

<refactor type="rename.relationshipProperty">
  <parameters>
    <parameter name="from">roles</parameter>
    <parameter name="to">rollen</parameter>
    <!-- Optional custom query -->
    <!--
    <parameter name="customQuery"><![CDATA[
      MATCH (n:Movie) <-[r:ACTED_IN] -() WHERE n.title =~ '.*Matrix.*' RETURN r
    ]]></parameter>
    -->
    <!-- Optional batch size (requires Neo4j 4.4+) -->
    <!--
    <parameter name="batchSize">23</parameter>
    -->
  </parameters>
</refactor>

添加代理键

您可以使用 Neo4j-Migrations 为您的节点和关系添加代理键(也称为技术键)。这对于从内部 Neo4j id(例如id()(Neo4j 4.4 及更早版本)或elementId())迁移非常有用。虽然这些函数很有用,并且一些对象图映射器可以直接使用它们,但它们通常不是您想要的

  • 您将数据库内部公开为自己的技术键的代理。

  • 您的业务现在依赖于数据库生成它们的方式。

  • 它们可能会被重用(在 Neo4j 内部),让您无法获得标识符的良好保证。

我们的内置重构使用randomUUID()为具有给定标签集的节点或具有匹配类型的关系分配一个UUID到名为id的属性,对于不存在此类属性的节点或关系。生成器和属性的名称都可以单独配置。此外,两种类型的实体都可以使用自定义查询进行匹配。

清单 4. 将随机 UUID 作为 id 添加到Movie节点(XML)
<refactor type="addSurrogateKeyTo.nodes">
  <parameters>
    <parameter name="labels">
      <value>Movie</value>
    </parameter>
  </parameters>
</refactor>
清单 5. 将随机 UUID 作为 id 添加到Movie节点(Java)
var addSurrogateKey = AddSurrogateKey.toNodes("Movie");
清单 6. 将随机 UUID 作为 id 添加到ACTED_IN关系(XML)

<refactor type="addSurrogateKeyTo.relationships">
  <parameters>
    <parameter name="type">ACTED_IN</parameter>
  </parameters>
</refactor>
清单 7. 将随机 UUID 作为 id 添加到 ACTED_IN 关系中 (Java)
var addSurrogateKey = AddSurrogateKey.toRelationships("ACTED_IN");

以下示例使用不同的目标属性并将内部 id 硬复制到属性中。当然,您可以使用您自己的用户定义函数来生成键。单个 %s 将被替换为保存匹配实体的变量。关系的语法相同(如上所示)

清单 8. 使用不同的属性和生成器函数 (XML)
<refactor type="addSurrogateKeyTo.nodes">
  <parameters>
    <parameter name="labels">
      <value>Movie</value>
    </parameter>
    <parameter name="property">movie_pk</parameter>
    <parameter name="generatorFunction">id(%s)</parameter>
  </parameters>
</refactor>
清单 9. 使用不同的属性和生成器函数 (Java)
var addSurrogateKey = AddSurrogateKey.toNodes("Movie")
  .withProperty("movie_pk")
  .withGeneratorFunction("id(%s)");

将 BTREE 索引迁移到“未来”索引

Neo4j 4.4 引入了 未来索引RANGEPOINT,它们取代了 Neo4j 4.x 中众所周知的 BTREE 索引。这些新索引从 Neo4j 4.4 开始可用,但不会参与 Neo4j 4.4 中的任何查询计划。它们在 Neo4j 4.4 中仅仅是为了迁移目的而存在:Neo4j 5.0 完全不支持 BTREE 索引。这意味着包含 BTREE 索引的数据库无法升级到 Neo4j 5.0。在尝试升级之前,需要先删除现有的 BTREE 索引。为此目的创建了类 ac.simons.neo4j.migrations.core.refactorings.MigrateBTreeIndexes。它允许创建匹配的新索引,并在升级存储之前选择性地删除 Neo4j 5.0 及更高版本中不再支持的索引。

与所有其他重构一样,它可以在您自己的应用程序中以编程方式使用,也可以通过 Neo4j-Migrations 使用。

通过并行创建未来索引准备升级到 Neo4j 5.0

清单 10. 并行创建未来索引和旧索引
<refactor type="migrate.createFutureIndexes">
    <parameters> (1)
        <parameter name="suffix">_future</parameter> (2)
        <parameter name="excludes"> (3)
            <value>a</value>
            <value>b</value>
        </parameter>
        <parameter name="typeMapping"> (4)
            <mapping>
                <name>c</name>
                <type>POINT</type>
            </mapping>
            <mapping>
                <name>d</name>
                <type>TEXT</type>
            </mapping>
        </parameter>
    </parameters>
</refactor>
1 所有参数都是可选的
2 默认后缀为 _new
3 可以使用 excludes 列表按名称排除要处理的项目。它的对应项是 includes 列表。如果后者不为空,则仅处理列表中的项目
4 默认情况下,创建 RANGE 索引。类型映射允许将特定的旧索引映射到 RANGEPOINTTEXT。迁移约束支持索引时,不会参考类型映射。

应用上述重构时,新的索引和约束将与旧索引并行创建。重构将记录删除旧约束的语句。

通过用未来索引替换 BTREE 索引准备升级到 Neo4j 5.0

这种方法的优点是,在进行存储升级之前不需要额外的手动工作。但是,存储升级应该紧随删除旧索引和创建替换索引之后进行,因为在实际升级到 Neo4j 5.0 或更高版本之前,后者根本不会参与计划。

清单 11. 用未来索引替换 BTREE 索引
<refactor type="migrate.replaceBTreeIndexes">
    <parameters>
        <parameter name="includes">
            <value>x</value>
            <value>y</value>
        </parameter>
    </parameters>
</refactor>

不支持 suffix 参数,因为它不需要。其他参数与 migrate.createFutureIndexes 具有相同的含义。以上示例显示了 includes 参数。

注解处理

Neo4j-Migrations 为 SDN 6 提供注解处理,并生成包含所有 @Node 实体唯一约束的 目录,使用分配的 ID 或外部生成的 ID(通过 @Id 加上可选的外部 @GeneratedValue 或不带其他注解)。

这符合 SDN 6 的最佳实践建议

  • 使用外部分配或生成的 ID 而不是 Neo4j 内部 ID 值(尤其是在将这些 ID 提供给外部系统时)

  • 至少为它们创建索引,最好是唯一约束,以确保任何分配的值都适合其用途

有关更多想法和思考,请查看 如何为数据库实体选择唯一标识符。虽然这篇文章仍然是从 SDN5+OGM 的角度出发,但其核心思想仍然适用。

注解处理器可在以下坐标下使用

清单 12. 注解处理器作为 Maven 依赖项
<dependency>
    <groupId>eu.michael-simons.neo4j</groupId>
    <artifactId>neo4j-migrations-annotation-processor</artifactId>
    <version>2.0.3</version>
</dependency>

除了 Neo4j-Migrations 本身之外,它没有其他依赖项(既没有 SDN6 也没有 Neo4j-OGM),因此可以安全地直接将其用作依赖项,以便所有最新的 Java 编译器都能选择它,或者作为编译器的专用处理器

清单 13. 在 Maven pom 中配置为编译器插件的处理器的注解处理器
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <annotationProcessorPath>
                <groupId>eu.michael-simons.neo4j</groupId>
                <artifactId>neo4j-migrations-annotation-processor</artifactId>
                <version>2.0.3</version>
            </annotationProcessorPath>
        </annotationProcessorPaths>
        <compilerArgs>
            <arg>-Aorg.neo4j.migrations.catalog_generator.default_catalog_name=R${next-migration-version}__Create_sdn_constraints.xml</arg>
            <arg>-Aorg.neo4j.migrations.catalog_generator.output_dir=my-generated-migrations</arg>
        </compilerArgs>
    </configuration>
</plugin>

后一种方法允许将其他配置传递给处理器,例如相对于 target/generated-sources 的输出位置和各种名称生成器。处理器有一个有限的 API 位于 neo4j-migrations-annotation-processor-api 模块中,例如 ac.simons.neo4j.migrations.annotations.proc.ConstraintNameGeneratorCatalogNameGenerator。您可以提供实现,但它们必须位于不受编译影响的项目之外,否则我们无法加载这些类。所有实现必须提供一个默认的、公开可访问的构造函数,或者——如果它们接受任何嵌套选项——一个接受类型为 Map<String, String> 的单个参数的公共构造函数。

生成器的范围是有意限制的:它将生成有效的目录声明,默认情况下为 <apply /> 操作。后者是安全的,因为目录在内部绑定到它们的迁移版本,并且在目录的 v2 中添加或更改的元素将被追加,不会从已知的目录中删除任何元素。可选地,可以将生成器配置为生成 reset 目录,这将从给定版本开始重新生成目录。

生成器不会在已知的迁移目录中生成迁移,也不会使用 Neo4j-Migrations 默认情况下会拾取的名称。您的任务是配置构建系统,以便任何生成的迁移都将

  • 具有识别的命名方案

  • 一个计算结果为正确排序的版本号的名称

  • 成为 Neo4j-Migrations 配置为拾取的目标中的目录的一部分

采用上述处理器的配置,进一步采取的一个示例方式是

清单 14. 将生成的迁移添加到实际的目标目录
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-resources</id>
            <goals>
                <goal>copy-resources</goal>
            </goals>
            <phase>process-classes</phase>
            <configuration>
                <outputDirectory>${project.build.outputDirectory}/neo4j/migrations/</outputDirectory>
                <resources>
                    <resource>
                        <directory>${project.build.directory}/generated-sources/annotations/my-generated-migrations</directory>
                        <filtering>false</filtering>
                    </resource>
                </resources>
            </configuration>
        </execution>
    </executions>
</plugin>

这在我们的示例中有效,但请记住:迁移将始终重新生成。只要您没有以任何导致新索引或修改索引的方式更改注释模型(重命名属性、标签等),这就可以了。

如果您的数据库中可用,生成器将始终使用幂等版本的索引。它们与可重复迁移配合良好。因此,一种解决方案是配置生成器以生成类似 R1_2_3__Create_domain_indexes.xml 的名称。

一种方法是将处理器添加到您的构建中,并使用上一个“良好”生成的目录和新目录进行差异比较。如果它不同,请在递增的版本号下添加新目录。

一个更简单的方法是使用一个与您的目标开发数据库连接的名称生成器,使用 Migrations 实例和我们的 api(MigrationChain info = migrations.info(MigrationChain.ChainBuilderMode.REMOTE);)从 info 实例(通过 .getLastAppliedVersion)获取最新应用的版本,并获取该版本并递增它,如果它发生了变化,则使用新版本添加目录,否则重用旧名称。

为命名生成提供了 API,对于其余部分,maven-resources-plugin 和可能 build-helper-maven-plugin 很有用。做出委托这项工作的决定是因为在该工具中为所有不同的设置和构建系统的组合提出一个一刀切的解决方案相当困难。

可以通过 -Aorg.neo4j.migrations.catalog_generator.naming_options=<nestedproperties> 将选项传递给名称生成器,其中 nestedproperties 遵循类似 a=x,b=y 的结构等等。如果要使用它,您自己的名称生成器必须提供一个接受单个 Map<String, String> 参数的公共构造函数。

我们推荐的方法是直接使用 javac 并在您的 CI/CD 系统中编写其调用脚本,如下段所示!

其他注解

我们提供了一组其他注解——@Unique@Required,它们可以独立使用,也可以与 SDN6 OGM 一起使用,以指定类上的约束。请查看这些注解的 JavaDoc 以了解其用法。如下所示的模块没有依赖项,既没有依赖于 Neo4j-Migrations,也没有依赖于 SDN6 或 OGM。虽然它与 SDN6 非常适合指定其他信息,但所有注解都提供了一种定义标签和关系类型的方式。

清单 15. 注解处理器作为 Maven 依赖项
<dependency>
    <groupId>eu.michael-simons.neo4j</groupId>
    <artifactId>neo4j-migrations-annotation-catalog</artifactId>
    <version>2.0.3</version>
</dependency>

结合 SDN6,有效的定义如下所示

import java.util.UUID;

import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;

import ac.simons.neo4j.migrations.annotations.catalog.Required;
import ac.simons.neo4j.migrations.annotations.catalog.Unique;

@Node
public record Organization(
	@Id @GeneratedValue @Unique UUID id, (1)
	@Required String name) {
}
1 从技术上讲,这里不需要 @Unique 注解,处理器将开箱即用地为此字段生成约束,但我们认为这样读起来更好。

使用 Javac 和我们的注解处理器

注解处理器本身由 3 个工件组成

neo4j-migrations-2.0.3.jar

需要生成目录

neo4j-migrations-annotation-processor-api-2.0.3.jar

包含 API 和内置注解

neo4j-migrations-annotation-processor-2.0.3.jar

处理器本身

您需要确保将所有这些都包含在处理器路径中,否则您很可能会看到类似 error: Bad service configuration file, or exception thrown while constructing Processor object: javax.annotation.processing.Processor: ac.simons.neo4j.migrations.annotations.proc.impl.CatalogGeneratingProcessor Unable to get public no-arg constructor 的内容,这有点误导性。

对于 OGM 实体

您至少需要 neo4j-ogm-core 作为依赖项才能处理 Neo4j-OGM 实体,并且很可能所有您习惯在这些实体中的 OGM 注解之外使用的库。以下语句在 output 目录中生成 V01__Create_OGM_schema.xml。它只执行注解处理

清单 16. 从 Neo4j-OGM 实体生成目录
javac -proc:only \
-processorpath neo4j-migrations-2.0.3.jar:neo4j-migrations-annotation-processor-api-2.0.3.jar:neo4j-migrations-annotation-processor-2.0.3.jar \
-Aorg.neo4j.migrations.catalog_generator.output_dir=output \
-Aorg.neo4j.migrations.catalog_generator.default_catalog_name=V01__Create_OGM_schema.xml \
-cp neo4j-ogm-core-4.0.0.jar \
extensions/neo4j-migrations-annotation-processing/processor/src/test/java/ac/simons/neo4j/migrations/annotations/proc/ogm/*

对于 SDN 实体

这里唯一的区别是您必须使用 SDN 6.0+ 及其依赖项作为 JavaC 的依赖项

清单 17. 从 Neo4j-OGM 实体生成目录
javac -proc:only \
-processorpath neo4j-migrations-2.0.3.jar:neo4j-migrations-annotation-processor-api-2.0.3.jar:neo4j-migrations-annotation-processor-2.0.3.jar \
-Aorg.neo4j.migrations.catalog_generator.output_dir=output \
-Aorg.neo4j.migrations.catalog_generator.default_catalog_name=V01__Create_SDN6_schema.xml \
-cp apiguardian-api-1.1.2.jar:spring-data-commons-2.7.2.jar:spring-data-neo4j-6.3.2.jar \
extensions/neo4j-migrations-annotation-processing/processor/src/test/java/ac/simons/neo4j/migrations/annotations/proc/sdn6/movies/*

对于使用目录注解注释的类

除了专用的注解之外,不需要其他 jar

清单 18. 从纯注解类生成目录
javac -proc:only \
-processorpath neo4j-migrations-2.0.3.jar:neo4j-migrations-annotation-processor-api-2.0.3.jar:neo4j-migrations-annotation-processor-2.0.3.jar \
-Aorg.neo4j.migrations.catalog_generator.output_dir=output \
-Aorg.neo4j.migrations.catalog_generator.default_catalog_name=R01__Create_annotated_schema.xml \
-cp neo4j-migrations-annotation-catalog-2.0.3 \
extensions/neo4j-migrations-annotation-processing/processor/src/test/java/ac/simons/neo4j/migrations/annotations/proc/catalog/valid/CoffeeBeanPure*

扩展

CSV 支持(实验性)

它做了什么?

此模块包含一些抽象基类,可帮助您在迁移期间使用 CSV 文件中的数据。我们的想法是您有一些 CSV 数据,您想使用 LOAD CSV。根据数据是否已更改,您需要重复迁移或不重复迁移。

我们基本上已经准备就绪

  • 可以重复或不重复的基于 Java 的迁移

  • 基于任何内容的校验和。

我们可以为您做的是为您检查 HTTP url 上的 CSV 数据的校验和。您需要做的是使它们对 Neo4j 和此工具都可用,并提供一个查询来处理它们。我们的工具将它们整合在一起。本质上,您希望从 ac.simons.neo4j.migrations.formats.csv.AbstractLoadCSVMigration 继承,如下所示

清单 19. R050__LoadBookData.java
import java.net.URI;

import org.neo4j.driver.Query;

import ac.simons.neo4j.migrations.formats.csv.AbstractLoadCSVMigration;

public class R050__LoadBookData extends AbstractLoadCSVMigration {

    public R050__LoadBookData() {
        super(URI.create("https://raw.githubusercontent.com/michael-simons/goodreads/master/all.csv"), true);
    }

    @Override
    public Query getQuery() {
        // language=cypher
        return new Query("""
            LOAD CSV FROM '%s' AS row FIELDTERMINATOR ';'
            MERGE (b:Book {title: trim(row[1])})
            SET b.type = row[2], b.state = row[3]
            WITH b, row
            UNWIND split(row[0], '&') AS author
            WITH b, split(author, ',') AS author
            WITH b, ((trim(coalesce(author[1], '')) + ' ') + trim(author[0])) AS author
            MERGE (a:Person {name: trim(author)})
            MERGE (a)-[r:WROTE]->(b)
            WITH b, a
            WITH b, collect(a) AS authors
            RETURN b.title, b.state, authors
            """);
    }
}

在以上示例中,我们认为CSV数据可能会发生变化,因此我们在构造函数调用中指示此迁移是可重复的。如果确实如此,我们建议使用反映此情况的类名。如果在构造期间使用false,则如果数据发生更改,迁移将失败。此处使用的Cypher执行合并操作,因此,我们之前已向标题和人员姓名添加了约束。您可以选择省略查询模板中的%s,但我们建议将其用于URI。

AsciiDoctor支持(实验性)

它有什么作用?

请打开此README.adoc,不仅查看渲染后的视图,还要查看原始的AsciiDoctor版本!

当作为外部库添加到受支持的用例场景之一时,它允许Neo4j-Migrations发现AsciiDoctor文件并将其用作Cypher语句的来源,以定义重构。

基于AsciiDoctor的迁移可以具有零到多个类型为cypher的代码块,其ID与我们的版本控制方案匹配,并且包含有效的内联Cypher内容。块定义如下所示

[source,cypher,id=V1.0__Create_initial_data]
----
// Your Cypher based migration
----

事实上,此README.adoc本身就是迁移的来源。它包含以下重构

CREATE (a:Author {
  id: randomUUID(),
  name: 'Stephen King'
})
CREATE (b:Book {
  id: randomUUID(),
  name: 'The Dark Tower'
})
CREATE (a)-[:WROTE]->(b)

我们可以拥有任意数量的迁移。

MATCH (a:Author {
  name: 'Stephen King'
})
CREATE (b:Book  {
  id: randomUUID(),
  name: 'Atlantis'
})
CREATE (a)-[:WROTE]->(b);


CREATE (a:Author {
  id: randomUUID(),
  name: 'Grace Blakeley'
})
CREATE (b:Book {
  id: randomUUID(),
  name: 'Stolen: How to Save the World From Financialisation'
})
CREATE (a)-[:WROTE]->(b);

为了使对人员姓名进行查询的性能更快,我们应该添加一些索引和约束。我们使用一个单独的文档V1.2__Create_id_constraints.xml来完成此操作,并将该文档包含在此处

<?xml version="1.0" encoding="UTF-8"?>
<migration xmlns="https://michael-simons.github.io/neo4j-migrations">
  <catalog>
    <indexes>
      <index name="idx_author_name">
        <label>Author</label>
        <properties>
          <property>name</property>
        </properties>
      </index>
      <index name="idx_book_name">
        <label>Book</label>
        <properties>
          <property>name</property>
        </properties>
      </index>
    </indexes>
    <constraints>
      <constraint name="unique_id_author" type="unique">
        <label>Author</label>
        <properties>
          <property>id</property>
        </properties>
      </constraint>
      <constraint name="unique_id_book" type="unique">
        <label>Book</label>
        <properties>
          <property>id</property>
        </properties>
      </constraint>
    </constraints>
  </catalog>

  <apply/>
</migration>
包含文件**不会**被处理。为了使系统分别处理上述xml内容或任何包含的Cypher文件,这些文件必须位于已配置的位置,如手册中所述。
我们选择不解析包含文件有两个原因:当仅处理内联代码时,更容易理解迁移的来源;此外,包含任意URL可能会带来安全风险。
请查看**此**文件本身的源代码,以了解哪些内容有效,哪些内容无效。

以下代码块是包含的Cypher文件的示例,当应用此更改集时,它将从其自身位置使用,但仍然可以在此文档中引用

CREATE (m:User {
  name: 'Michael'
})
WITH m
MATCH (a:Author {
  name: 'Stephen King'
})-[:WROTE]->(b)
WITH m, a, collect(b) AS books
CREATE (m)-[:LIKES]->(a)
WITH m, books
UNWIND books AS b
CREATE (m)-[:LIKES]->(b);

基于AsciiDoctor的迁移的校验和是针对每个Cypher块单独计算的,而不是针对整个文件。因此,一个AsciiDoctor文件基本上充当许多迁移的容器。

如何使用它?

扩展程序通过服务加载器加载。在标准的Spring Boot或Quarkus应用程序中,您只需要添加一个额外的依赖项

清单20. AsciiDoctor扩展作为Maven依赖项
<dependency>
    <groupId>eu.michael-simons.neo4j</groupId>
    <artifactId>neo4j-migrations-formats-adoc</artifactId>
    <version>2.0.3</version>
</dependency>

或者如果您喜欢Gradle

清单21. AsciiDoctor扩展作为Gradle依赖项
dependencies {
    implementation 'eu.michael-simons.neo4j:neo4j-migrations-formats-adoc:2.0.3'
}

就是这样。

对于CLI,您应该从Maven Central下载-all工件:neo4j-migrations-formats-adoc-2.0.3-all.jar 这仅适用于基于JVM的CLI版本,该版本可在此处获得。

完整的示例如下所示

curl -LO https://github.com/michael-simons/neo4j-migrations/releases/download/2.0.3/neo4j-migrations-2.0.3.zip
curl -LO https://repo.maven.apache.org/maven2/eu/michael-simons/neo4j/neo4j-migrations-formats-adoc/2.0.3/neo4j-migrations-formats-adoc-2.0.3-all.jar
unzip neo4j-migrations-2.0.3.zip
cd neo4j-migrations-2.0.3
CLASSPATH_PREFIX=../neo4j-migrations-formats-adoc-2.0.3-all.jar \
  bin/neo4j-migrations --password secret \
  --location file:///path/to/neo4j/adoc-migrations \
  info

这将导致

neo4j@localhost:7687 (Neo4j/4.4.4)
Database: neo4j

+---------+---------------------------+---------+---------+----------------------------------------------+
| Version | Description               | Type    | State   | Source                                       |
+---------+---------------------------+---------+---------+----------------------------------------------+
| 1.0     | initial data              | CYPHER  | PENDING | initial_schema_draft.adoc#V1.0__initial_data |
| 1.2     | more data                 | CYPHER  | PENDING | initial_schema_draft.adoc#V1.2__more_data    |
| 2.0     | lets rock                 | CYPHER  | PENDING | more_content.adoc#V2.0__lets_rock            |
| 3.0     | We forgot the constraints | CATALOG | PENDING | V3.0__We_forgot_the_constraints.xml          |
| 4.0     | Plain cypher              | CYPHER  | PENDING | V4.0__Plain_cypher.cypher                    |
+---------+---------------------------+---------+---------+----------------------------------------------+

(注意:为了简洁起见,已省略空列。)

Markdown支持(实验性)

它有什么作用?

当作为外部库添加到受支持的用例场景之一时,它允许Neo4j-Migrations发现Markdown文件并将其用作Cypher语句的来源,以定义重构。

基于Markdown的迁移可以具有零到多个带围栏的代码块,其ID与我们的版本控制方案匹配,并且包含有效的内联Cypher内容。块定义如下所示

```id=V1.0__Create_initial_data
// Your Cypher based migration

如何使用它?

扩展程序通过服务加载器加载。在标准的Spring Boot或Quarkus应用程序中,您只需要添加一个额外的依赖项

清单22. Markdown扩展作为Maven依赖项
<dependency>
    <groupId>eu.michael-simons.neo4j</groupId>
    <artifactId>neo4j-migrations-formats-markdown</artifactId>
    <version>2.0.3</version>
</dependency>

或者如果您喜欢Gradle

清单23. AsciiDoctor扩展作为Gradle依赖项
dependencies {
    implementation 'eu.michael-simons.neo4j:neo4j-migrations-formats-markdown:2.0.3'
}

就是这样。

对于CLI,您应该从Maven Central下载-all工件:neo4j-migrations-formats-markdown-2.0.3-all.jar 这仅适用于基于JVM的CLI版本,该版本可在此处获得。

完整的示例如下所示

curl -LO https://github.com/michael-simons/neo4j-migrations/releases/download/2.0.3/neo4j-migrations-2.0.3.zip
curl -LO https://repo.maven.apache.org/maven2/eu/michael-simons/neo4j/neo4j-migrations-formats-markdown/2.0.3/neo4j-migrations-formats-markdown-2.0.3-all.jar
unzip neo4j-migrations-2.0.3.zip
cd neo4j-migrations-2.0.3
CLASSPATH_PREFIX=../neo4j-migrations-formats-markdown-2.0.3-all.jar \
  bin/neo4j-migrations --password secret \
  --location file:///path/to/neo4j/markdown-migrations \
  info

这将导致

neo4j@localhost:7687 (Neo4j/4.4.8)
Database: neo4j

+---------+---------------------+--------+---------+--------------------------------------------+
| Version | Description         | Type   | State   | Source                                     |
+---------+---------------------+--------+---------+--------------------------------------------+
| 1.0     | initial data        | CYPHER | PENDING | initial_schema_draft.md#V1.0__initial_data |
| 1.2     | more data           | CYPHER | PENDING | initial_schema_draft.md#V1.2__more_data    |
| 1.3     | something different | CYPHER | PENDING | more_content.md#V1.3__something_different  |
+---------+---------------------+--------+---------+--------------------------------------------+

(注意:为了简洁起见,已省略空列。)

© . All rights reserved.