Schema Management

Apache Cassandra 是一个在任何数据交互之前需要模式定义的数据存储。Spring Data for Apache Cassandra 可以用模式创建来支持你。

Keyspaces and Lifecycle Scripts

首先从一个 Cassandra 键空间开始。键空间是共享相同副本因子和副本策略的表的逻辑分组。键空间管理位于 CqlSession 配置中,其中有 KeyspaceSpecification 以及启动和关闭 CQL 脚本执行。

使用规范声明一个键空间允许创建和删除 Keyspace。它从规范中获取 CQL,这样你就不用自己编写 CQL。以下示例使用 XML 指定了一个 Cassandra 键空间:

Example 1. Specifying a Cassandra keyspace
Java
link:example$CreateKeyspaceConfiguration.java[role=include]
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:cassandra="http://www.springframework.org/schema/data/cassandra"
  xsi:schemaLocation="
    http://www.springframework.org/schema/data/cassandra
    https://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd
    http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">

    <cassandra:session>

        <cassandra:keyspace action="CREATE_DROP" durable-writes="true" name="my_keyspace">
            <cassandra:replication class="NETWORK_TOPOLOGY_STRATEGY">
              <cassandra:data-center name="foo" replication-factor="1" />
              <cassandra:data-center name="bar" replication-factor="2" />
            </cassandra:replication>
      </cassandra:keyspace>

    </cassandra:session>
</beans>

密钥空间创建允许快速引导而无需外部密钥空间管理。这可能对某些场景有用,但应小心使用。在应用程序关闭时删除密钥空间会移除密钥空间以及密钥空间中表中的所有数据。

Initializing a SessionFactory

org.springframework.data.cassandra.core.cql.session.init 包提供的支持用于初始化一个现有的 SessionFactory。你有时可能需要初始化一个在服务器上某个地方运行的键空间。

Initializing a Keyspace

你可以按照以下 Java 配置示例所示,在已配置的键空间中提供在 CqlSession 初始化和关闭时执行的任意 CQL:

Java
link:example$KeyspacePopulatorConfiguration.java[role=include]
XML
<cassandra:initialize-keyspace session-factory-ref="cassandraSessionFactory">
    <cassandra:script location="classpath:com/foo/cql/db-schema.cql"/>
    <cassandra:script location="classpath:com/foo/cql/db-test-data.cql"/>
</cassandra:initialize-keyspace>

前一个示例在键空间中运行指定的两个脚本。第一个脚本创建一个模式,而第二个脚本用一个测试数据集填充表。脚本位置也可以是模式,并且采用 Spring 中用于资源的通常 Ant 样式(例如,classpath*:/com/foo/*/cql/-data.cql)。如果你使用一个模式,则这些脚本将根据它们的 URL 或文件名按字典顺序运行。

键空间初始化程序的默认行为是无条件运行所提供的脚本。这可能不总是你想要的——例如,如果你在已经包含测试数据的键空间上运行这些脚本。遵循先创建表然后插入数据的通用模式(之前已显示)可以减少意外删除数据的可能性。如果表已经存在,则第一步将失败。

但是,为了能够更好地控制现有数据的创建和删除,XML 命名空间提供了一些附加选项。第一个选项是一个开关,用于打开和关闭初始化。你可以根据环境设置该选项(例如,从系统属性或环境 bean 中获取一个布尔值)。以下示例从系统属性获取一个值:

<cassandra:initialize-keyspace session-factory-ref="cassandraSessionFactory"
    enabled="#{systemProperties.INITIALIZE_KEYSPACE}">    1
    <cassandra:script location="..."/>
</cassandra:initialize-database>
1 从名为 INITIALIZE_KEYSPACE 的系统属性中获取 enabled 的值。

控制现有数据发生什么事情的第二个选项是对失败更加宽容。为此,你可以控制初始化程序忽略它从脚本执行的 CQL 中的某些错误的能力,如下例所示:

Java
link:example$KeyspacePopulatorFailureConfiguration.java[role=include]
XML
<cassandra:initialize-keyspace session-factory-ref="cassandraSessionFactory" ignore-failures="DROPS">
    <cassandra:script location="..."/>
</cassandra:initialize-database>

在前一个示例中,我们说我们期望有时这些脚本在空键空间上运行,并且脚本中有一些 DROP 语句,因此会失败。因此已失败的 CQL DROP 语句将被忽略,但是其他失败将导致一个异常。如果你不想使用支持 DROP …​ IF EXISTS (或类似项),但是你想在重新创建之前无条件删除所有测试数据,则此操作非常有用。在这种情况下,第一个脚本通常是一组 DROP 语句,后面是一组 CREATE 语句。

ignore-failures 选项可以设置为 NONE(默认值)、DROPS(忽略失败的删除)或 ALL(忽略所有失败)。

如果脚本中根本不包含 ; 字符,那么每条语句都应该使用 ; 或一个新建行进行分隔。你可以进行全局控制或逐个脚本控制,如下例所示:

Java
link:example$SessionFactoryInitializerConfiguration.java[role=include]
XML
<cassandra:initialize-keyspace session-factory-ref="cassandraSessionFactory" separator="@@">
    <cassandra:script location="classpath:com/myapp/cql/db-schema.cql" separator=";"/>
    <cassandra:script location="classpath:com/myapp/cql/db-test-data-1.cql"/>
    <cassandra:script location="classpath:com/myapp/cql/db-test-data-2.cql"/>
</cassandra:initialize-keyspace>

在这个示例中,两个 test-data 脚本使用 @@ 作为语句分隔符,而只有 db-schema.cql 使用 ;。该配置指定默认分隔符是 @@,并为 db-schema 脚本覆盖该默认值。

如果你需要比从 XML 命名空间获得的更多控制,则可以使用 SessionFactoryInitializer 并将其定义为应用程序中的组件。

Initialization of Other Components that Depend on the Keyspace

很大一部分应用程序(在 Spring 上下文开始后才使用数据库的应用程序)可以使用数据库初始化器,而无需进一步进行复杂化。如果你的应用程序不属于这些应用程序,则你可能需要阅读本节的其余部分。

数据库初始化器依赖于一个 SessionFactory 实例,并运行其初始化回调中提供的脚本(与 XML bean 定义中的 init-method、组件中的 @PostConstruct 方法或实现 InitializingBean 的组件中的 afterPropertiesSet() 方法类似)。如果其他 bean 依赖于相同的数据源并使用初始化回调中的会话工厂,则可能会出问题,因为数据尚未初始化。一个常见的例子是一个在应用程序启动时立即初始化并从数据库加载数据的缓存。

为了解决这个问题,你有两个选择:将缓存初始化策略更改为较晚的阶段,或确保键空间初始化器首先初始化。

如果你控制应用程序并且没有其他方式,则更改缓存初始化策略可能会很容易。以下列出了如何实现此目的的一些建议:

  • 使高速缓存延迟在首次使用时初始化,这可以改善应用程序的启动时间。

  • 让高速缓存或初始化高速缓存的单独组件实现 LifecycleSmartLifecycle。当应用程序上下文启动时,您可以通过设置其 autoStartup 标志自动启动 SmartLifecycle,并且可以通过调用外围上下文上的 ConfigurableApplicationContext.start() 来手动启动 Lifecycle

  • 使用 Spring ApplicationEvent 或类似的自定义观察器机制来触发高速缓存初始化。ContextRefreshedEvent 始终由上下文在它可以随时使用时(在所有 Bean 初始化之后)发布,因此它经常是一个有用的钩子(这是 SmartLifecycle 的默认工作方式)。

确保密钥空间初始化程序首先进行初始化也很容易。有关如何实现此操作的一些建议包括:

  • 依赖于 Spring BeanFactory 的默认行为,它在于 Bean 以注册顺序初始化。通过采用 XML 配置中一组 &lt;import/&gt; 元素的通用实践来对您的应用程序模块进行排序,并确保数据库和数据库初始列首先列出,可以轻松排列它。

  • SessionFactory 和使用它的业务组件分开,并通过将它们放入单独的 ApplicationContext 实例中来控制它们的启动顺序(例如,父上下文包含 SessionFactory,并且子上下文包含业务组件)。这个结构在 Spring Web 应用程序中很常见,但可以更广泛地应用。

  • 使用 Tables and User-defined Types 的模式管理来初始化键空间,方法是使用 Spring Data Cassandra 内置的模式生成器。

Tables and User-defined Types

面向 Apache Cassandra 的 Spring Data 使用与数据模型相符的映射实体类来访问数据。你可以使用这些实体类来创建 Cassandra 表格规范和用户类型定义。

模式创建通过 SchemaAction 绑定到 CqlSession 初始化。支持以下操作:

  • SchemaAction.NONE: 不创建或删除任何表或类型。这是默认设置。

  • SchemaAction.CREATE: 根据使用 @Table 注释的实体和使用 @UserDefinedType 注释的类型创建表、索引和用户定义类型。如果尝试创建类型,现有表或类型会导致错误。

  • SchemaAction.CREATE_IF_NOT_EXISTS: 与 SchemaAction.CREATE 相同,但应用了 IF NOT EXISTS。现有表或类型不会导致任何错误,但可能保持陈旧。

  • SchemaAction.RECREATE: 删除和重新创建已知正在使用的现有表和类型。应用程序中未配置的表和类型不会被删除。

  • SchemaAction.RECREATE_DROP_UNUSED: 删除所有表和类型,并只重新创建已知的表和类型。

SchemaAction.RECREATESchemaAction.RECREATE_DROP_UNUSED 会删除表并丢失所有数据。RECREATE_DROP_UNUSED 还会删除应用程序不知道的表和类型。

Enabling Tables and User-Defined Types for Schema Management

Metadata-based Mapping解释了具有约定和注释的对象映射。为防止创建不需要的类作为表或类型,架构管理仅对使用 `@Table`注释的实体和使用 `@UserDefinedType`注释的用户定义类型处于活动状态。实体是通过扫描类路径发现的。实体扫描需要一个或多个基本包。使用 `TupleValue`的元组类型列不提供任何类型化详细信息。因此,您必须使用 `@CassandraType(type = TUPLE, typeArguments = …)`注释此类列属性以指定所需的列类型。

以下示例展示如何在 XML 配置中指定实体基本包:

Example 2. Specifying entity base packages
Java
link:example$EntityBasePackagesConfiguration.java[role=include]
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:cassandra="http://www.springframework.org/schema/data/cassandra"
  xsi:schemaLocation="
    http://www.springframework.org/schema/data/cassandra
    https://www.springframework.org/schema/data/cassandra/spring-cassandra.xsd
    http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">

    <cassandra:mapping entity-base-packages="com.foo,com.bar"/>
</beans>