模式管理

Apache Cassandra 是一种数据存储系统,在进行任何数据交互之前都需要先定义模式。 Spring Data for Apache Cassandra 可以帮助您完成模式创建。spring-doc.cadn.net.cn

CQL 规范

CQL 规范是一种抽象表示,用于描述 CQL DDL 操作,例如创建表或删除 keyspace。 规范适用于以下 CQL 对象类型:spring-doc.cadn.net.cn

Cassandra 的某些变体可以支持物化视图、用户自定义函数、角色以及更多类型的对象。 Spring Data for Apache Cassandra 仅为上述列出的类型提供规范说明。

这些可用于通过流畅的接口来创建(CREATE)、修改(ALTER)和删除(DROP)CQL 对象。SpecificationBuilder 是构建此类规范的入口点。 随后,您可以使用 CqlGenerator.toCql(…) 轻松地从规范生成 CQL。spring-doc.cadn.net.cn

请参见以下示例,以创建用于键空间(keyspace)创建、表(table)创建和索引(index)创建的规范。spring-doc.cadn.net.cn

示例 1. 指定 Cassandra keyspace
Java
CqlSpecification createKeyspace = SpecificationBuilder.createKeyspace("my_keyspace")
		.with(KeyspaceOption.REPLICATION, KeyspaceAttributes.newSimpleReplication())
		.with(KeyspaceOption.DURABLE_WRITES, true);

// results in CREATE KEYSPACE my_keyspace WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true
String cql = CqlGenerator.toCql(createKeyspace);
示例 2. 指定一个 Cassandra 表
Java
CreateTableSpecification createTable = CreateTableSpecification.createTable("my_table")
		.partitionKeyColumn("last_name", DataTypes.TEXT)
		.partitionKeyColumn("first_name", DataTypes.TEXT)
		.column("age", DataTypes.INT);

// results in CREATE TABLE my_table (last_name text, first_name text, age int, PRIMARY KEY(last_name, first_name))
String cql = CqlGenerator.toCql(createTable);
示例 3. 指定一个 Cassandra 索引
Java
CreateIndexSpecification spec = SpecificationBuilder.createIndex()
		.tableName("mytable").keys().columnName("column");

// results in CREATE INDEX ON mytable (KEYS(column))
String cql = CqlGenerator.toCql(createTable);

您可以将规范(specifications)与配置 API 结合使用,以定义 keyspace 的创建和 schema 操作。spring-doc.cadn.net.cn

键空间与生命周期脚本

首先需要创建的是一个 Cassandra keyspace(键空间)。 Keyspace 是一种逻辑分组,用于将具有相同复制因子和复制策略的表组织在一起。 Keyspace 管理位于 CqlSession 配置中,该配置包含 KeyspaceSpecification 以及启动和关闭时执行的 CQL 脚本。spring-doc.cadn.net.cn

通过规范声明 keyspace 可以实现 keyspace 的创建和删除。 它会根据该规范自动生成 CQL,因此你无需手动编写 CQL。 以下示例使用 XML 来指定一个 Cassandra keyspace:spring-doc.cadn.net.cn

示例 4. 指定 Cassandra keyspace
Java
@Configuration
public class CreateKeyspaceConfiguration extends AbstractCassandraConfiguration implements BeanClassLoaderAware {

	@Override
	protected List<CreateKeyspaceSpecification> getKeyspaceCreations() {

		CreateKeyspaceSpecification specification = SpecificationBuilder.createKeyspace("my_keyspace")
				.with(KeyspaceOption.DURABLE_WRITES, true)
				.withNetworkReplication(DataCenterReplication.of("foo", 1), DataCenterReplication.of("bar", 2));

		return Arrays.asList(specification);
	}

	@Override
	protected List<DropKeyspaceSpecification> getKeyspaceDrops() {
		return Arrays.asList(DropKeyspaceSpecification.dropKeyspace("my_keyspace"));
	}

	// ...
}
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>
Keyspace 创建允许快速引导启动,而无需依赖外部的 keyspace 管理。 这在某些场景下可能很有用,但应谨慎使用。 在应用程序关闭时删除 keyspace 会移除该 keyspace 及其所有表中的数据。

初始化SessionFactory

org.springframework.data.cassandra.core.cql.session.init 包提供了对初始化现有 SessionFactory 的支持。 有时您可能需要初始化某个服务器上运行的 keyspace。spring-doc.cadn.net.cn

初始化 Keyspace

您可以提供任意的 CQL 语句,这些语句将在配置的 keyspace 中于 CqlSession 初始化和关闭时执行,如下列 Java 配置示例所示:spring-doc.cadn.net.cn

Java
@Configuration
public class KeyspacePopulatorConfiguration extends AbstractCassandraConfiguration {

	@Nullable
	@Override
	protected KeyspacePopulator keyspacePopulator() {
		return new ResourceKeyspacePopulator(new ClassPathResource("com/foo/cql/db-schema.cql"),
				new ClassPathResource("com/foo/cql/db-test-data.cql"));
	}

	@Nullable
	@Override
	protected KeyspacePopulator keyspaceCleaner() {
		return new ResourceKeyspacePopulator(scriptOf("DROP TABLE my_table;"));
	}

	// ...
}
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>

前面的示例针对指定的 keyspace 运行两个脚本。 第一个脚本用于创建 schema,第二个脚本则使用测试数据集填充表。 脚本位置也可以采用 Spring 中资源常用的 Ant 风格通配符模式(例如,classpath*:/com/foo/**/cql/*-data.cql)。 如果使用了模式,则脚本将按照其 URL 或文件名的字典顺序执行。spring-doc.cadn.net.cn

键空间初始化器的默认行为是无条件地执行所提供的脚本。 这并不总是你想要的行为——例如,当你对一个已经包含测试数据的键空间运行这些脚本时。 通过采用常见的模式(如前所示),即先创建表,然后再插入数据,可以降低意外删除数据的可能性。 如果表已存在,第一步就会失败。spring-doc.cadn.net.cn

然而,为了更精细地控制现有数据的创建与删除,XML 命名空间提供了几个额外的选项。 第一个选项是一个用于开启或关闭初始化的标志。 你可以根据环境来设置该标志(例如从系统属性或环境 Bean 中获取一个布尔值)。 以下示例从系统属性中获取一个值:spring-doc.cadn.net.cn

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

控制现有数据处理方式的第二种选项是对失败采取更宽容的态度。 为此,您可以控制初始化器在执行脚本中的 CQL 时忽略某些错误的能力,如下例所示:spring-doc.cadn.net.cn

Java
@Configuration
public class KeyspacePopulatorFailureConfiguration extends AbstractCassandraConfiguration {

	@Nullable
	@Override
	protected KeyspacePopulator keyspacePopulator() {

		ResourceKeyspacePopulator populator = new ResourceKeyspacePopulator(
				new ClassPathResource("com/foo/cql/db-schema.cql"));

		populator.setIgnoreFailedDrops(true);

		return populator;
	}

	// ...
}
XML
<cassandra:initialize-keyspace session-factory-ref="cassandraSessionFactory" ignore-failures="DROPS">
    <cassandra:script location="..."/>
</cassandra:initialize-database>

在前面的示例中,我们指出有时脚本会针对一个空的 keyspace 执行,而脚本中包含一些 DROP 语句,因此这些语句会失败。 因此,失败的 CQL DROP 语句将被忽略,但其他类型的失败仍会引发异常。 如果你不想使用 DROP …​ IF EXISTS(或类似语法)的支持,但又希望在重新创建测试数据之前无条件地删除所有测试数据,这种做法就非常有用。 在这种情况下,第一个脚本通常是一组 DROP 语句,后面紧跟一组 CREATE 语句。spring-doc.cadn.net.cn

ignore-failures 选项可设置为 NONE(默认值)、DROPS(忽略失败的删除操作)或 ALL(忽略所有失败)。spring-doc.cadn.net.cn

如果脚本中完全不包含 ; 字符,则每个语句应通过 ; 或换行符进行分隔。 您可以全局控制此行为,也可以针对每个脚本单独控制,如下例所示:spring-doc.cadn.net.cn

Java
@Configuration
public class SessionFactoryInitializerConfiguration extends AbstractCassandraConfiguration {

	@Bean
	SessionFactoryInitializer sessionFactoryInitializer(SessionFactory sessionFactory) {

		SessionFactoryInitializer initializer = new SessionFactoryInitializer();
		initializer.setSessionFactory(sessionFactory);

		ResourceKeyspacePopulator populator1 = new ResourceKeyspacePopulator();
		populator1.setSeparator(";");
		populator1.setScripts(new ClassPathResource("com/myapp/cql/db-schema.cql"));

		ResourceKeyspacePopulator populator2 = new ResourceKeyspacePopulator();
		populator2.setSeparator("@@");
		populator2.setScripts(new ClassPathResource("classpath:com/myapp/cql/db-test-data-1.cql"), //
				new ClassPathResource("classpath:com/myapp/cql/db-test-data-2.cql"));

		initializer.setKeyspacePopulator(new CompositeKeyspacePopulator(populator1, populator2));

		return initializer;
	}

	// ...
}
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 脚本覆盖该默认值。spring-doc.cadn.net.cn

如果你需要比 XML 命名空间所提供的更精细的控制,可以直接使用 SessionFactoryInitializer,并将其定义为应用程序中的一个组件。spring-doc.cadn.net.cn

初始化依赖键空间的其它组件

一大类应用程序(那些在 Spring 上下文启动之后才使用数据库的应用)可以直接使用数据库初始化器,而不会产生其他复杂问题。 如果你的应用程序不属于这类,你可能需要阅读本节的其余部分。spring-doc.cadn.net.cn

数据库初始化器依赖于一个 SessionFactory 实例,并在其初始化回调中执行所提供的脚本(类似于 XML bean 定义中的 init-method、组件中的 @PostConstruct 方法,或实现了 afterPropertiesSet() 接口的组件中的 InitializingBean 方法)。 如果其他 Bean 依赖于相同的数据源并在其初始化回调中使用该 SessionFactory,则可能会出现问题,因为此时数据尚未完成初始化。 一个常见的例子是:缓存在应用启动时就急切地初始化,并从数据库中加载数据。spring-doc.cadn.net.cn

要解决此问题,您有两个选项:将缓存初始化策略更改为稍后的阶段,或者确保 keyspace 初始化器首先被初始化。spring-doc.cadn.net.cn

如果你能控制该应用程序,更改缓存初始化策略可能会很容易;否则则不然。 关于如何实现这一点的一些建议包括:spring-doc.cadn.net.cn

  • 使缓存在首次使用时延迟初始化,从而提升应用程序的启动时间。spring-doc.cadn.net.cn

  • 让你的缓存或负责初始化缓存的独立组件实现 LifecycleSmartLifecycle 接口。 当应用上下文启动时,你可以通过设置 SmartLifecycleautoStartup 标志来自动启动它;也可以通过对所属上下文调用 Lifecycle 方法来手动启动一个 ConfigurableApplicationContext.start()spring-doc.cadn.net.cn

  • 使用 Spring 的 ApplicationEvent 或类似的自定义观察者机制来触发缓存的初始化。ContextRefreshedEvent 在上下文准备就绪(所有 Bean 均已初始化完毕)时总会由上下文发布,因此这通常是一个有用的钩子(SmartLifecycle 默认就是通过这种方式工作的)。spring-doc.cadn.net.cn

确保键空间初始化器首先被初始化也很简单。 关于如何实现这一点的一些建议包括:spring-doc.cadn.net.cn

  • 依赖 Spring BeanFactory 的默认行为,即 Bean 按照注册顺序进行初始化。 你可以通过在 XML 配置中采用一组 <import/> 元素的常见做法来轻松实现这一点,这些元素用于对应用程序模块进行排序,并确保数据库及其初始化配置被列在最前面。spring-doc.cadn.net.cn

  • 通过将SessionFactory与其使用的业务组件分别置于独立的ApplicationContext实例中(例如,父上下文包含SessionFactory,子上下文包含业务组件),来分离3和业务组件,并控制它们的启动顺序。 这种结构在Spring Web应用程序中很常见,但也可以更广泛地应用。spring-doc.cadn.net.cn

  • 使用表和用户自定义类型的模式管理功能,通过 Spring Data Cassandra 内置的模式生成器来初始化 keyspace。spring-doc.cadn.net.cn

表和自定义类型

Spring Data for Apache Cassandra 通过映射的实体类来实现数据访问,这些实体类与您的数据模型相匹配。 您可以使用这些实体类来创建 Cassandra 表结构定义和用户自定义类型(User Type)定义。spring-doc.cadn.net.cn

模式创建通过 CqlSessionSchemaAction 的初始化相关联。 支持以下操作:spring-doc.cadn.net.cn

  • SchemaAction.NONE:不会创建或删除任何表或类型。 这是默认设置。spring-doc.cadn.net.cn

  • SchemaAction.CREATE:根据使用 @Table 注解的实体类以及使用 @UserDefinedType 注解的类型创建表、索引和用户自定义类型。 如果尝试创建已存在的表或类型,则会引发错误。spring-doc.cadn.net.cn

  • SchemaAction.CREATE_IF_NOT_EXISTS:类似于 SchemaAction.CREATE,但会加上 IF NOT EXISTS 条件。 已存在的表或类型不会引发任何错误,但可能会保持过时状态。spring-doc.cadn.net.cn

  • SchemaAction.RECREATE:删除并重新创建已知会被使用的现有表和类型。 应用程序中未配置的表和类型不会被删除。spring-doc.cadn.net.cn

  • SchemaAction.RECREATE_DROP_UNUSED:删除所有表和类型,并仅重新创建已知的表和类型。spring-doc.cadn.net.cn

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

启用表和自定义类型以进行架构管理

基于元数据的映射介绍了使用约定和注解进行对象映射的方法。 为防止不需要的类被创建为表或类型,仅对标注了 @Table 的实体以及标注了 @UserDefinedType 的用户自定义类型启用模式管理。 实体通过扫描类路径(classpath)来发现。 实体扫描需要指定一个或多个基础包。 使用 TupleValue 的元组类型列不提供任何类型细节。 因此,您必须为此类列属性添加 @CassandraType(type = TUPLE, typeArguments = …) 注解, 以指定所需的列类型。spring-doc.cadn.net.cn

以下示例展示了如何在 XML 配置中指定实体基础包:spring-doc.cadn.net.cn

示例 5. 指定实体基础包
Java
@Configuration
public class EntityBasePackagesConfiguration extends AbstractCassandraConfiguration {

	@Override
	public String[] getEntityBasePackages() {
		return new String[] { "com.foo", "com.bar" };
	}

	// ...
}
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>