跳到主要内容
版本:5.1.1

模式管理

Doubao 中英对照 Schema Management

Apache Cassandra 是一种数据存储,在进行任何数据交互之前需要先定义模式。Spring Data for Apache Cassandra 可以在模式创建方面为您提供支持。

CQL 规约​

CQL 规约是一种抽象,用于表示 CQL DDL 操作,例如创建表或删除键空间。以下 CQL 对象类型提供了规约:

  • 键空间

  • 表

  • 索引

  • 用户定义类型

备注

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

这些规约可通过流式接口用于 CREATE、ALTER 和 DROP CQL 对象。SpecificationBuilder 是构建此类规约的入口点。之后您可以使用 CqlGenerator.toCql(…) 方便地从规约渲染 CQL。

请参阅以下示例,了解如何创建用于键空间创建、表创建和索引创建的规约。

示例 1. 指定 Cassandra 键空间

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 表

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 索引

CreateIndexSpecification spec = SpecificationBuilder.createIndex()
.tableName("mytable").keys().columnName("column");

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

您可以将规约与配置 API 结合使用,来定义键空间创建和模式操作。

键空间与生命周期脚本​

首先要从 Cassandra 键空间开始。键空间是表的逻辑分组,这些表共享相同的复制因子和复制策略。键空间管理位于 CqlSession 配置中,该配置包含 KeyspaceSpecification 以及启动和关闭时的 CQL 脚本执行。

用规约声明键空间可以创建和删除键空间。它从规约派生出 CQL,因此您无需自己编写 CQL。以下示例使用 XML 指定了一个 Cassandra 键空间:

示例 4. 指定 Cassandra 键空间

@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 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>
备注

键空间创建支持快速初始化,无需外部键空间管理。这在某些场景下很有用,但应谨慎使用。在应用关闭时删除键空间会从键空间中的表移除键空间及所有数据。

初始化 SessionFactory​

org.springframework.data.cassandra.core.cql.session.init 包为初始化已有的 SessionFactory 提供支持。有时您可能需要初始化一个运行在某处服务器上的键空间。

初始化键空间​

您可以提供任意 CQL,使其在 CqlSession 初始化和关闭时于所配置的键空间上执行,如下列 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;"));
}

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

前面的示例对键空间运行了两个指定脚本。第一个脚本创建模式,第二个脚本用测试数据集填充表。脚本位置也可以是带通配符的 Ant 风格模式(Spring 中资源常用的方式,例如 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>
  • 从名为 INITIALIZE_KEYSPACE 的系统属性获取 enabled 的值。

控制已有数据处理的第二个选项是对失败更宽容。为此,您可以控制初始化器在执行脚本中的 CQL 时忽略某些错误的能力,如下例所示:

@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;
}

// ...
}
<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(忽略所有失败)。

每条语句应以 ; 分隔;如果脚本中完全没有 ; 字符,则以换行分隔。您可以全局控制,也可以逐脚本控制,如下例所示:

@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;
}

// ...
}
<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,并将其定义为应用中的一个组件。

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

有一大类应用(那些在 Spring 上下文启动之后才使用数据库的应用)可以毫无额外复杂地使用数据库初始化器。如果您的应用不属于这一类,可能需要阅读本节的其余部分。

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

要解决这个问题,您有两个选择:把缓存初始化策略改到更晚的阶段,或确保键空间初始化器先被初始化。

如果应用在您的掌控之中,更改缓存初始化策略可能很容易,否则则不然。一些实现建议包括:

  • 让缓存在首次使用时延迟初始化,从而改善应用启动时间。

  • 让您的缓存或一个初始化缓存的独立组件实现 Lifecycle 或 SmartLifecycle。应用上下文启动时,您可以通过设置 autoStartup 标志自动启动 SmartLifecycle,也可以通过在封闭上下文上调用 ConfigurableApplicationContext.start() 手动启动 Lifecycle。

  • 使用 Spring ApplicationEvent 或类似的自定义观察者机制来触发缓存初始化。上下文在就绪可用时(所有 bean 初始化之后)总会发布 ContextRefreshedEvent,因此这通常是一个有用的钩子(SmartLifecycle 默认就是这样工作的)。

确保键空间初始化器先被初始化也很容易。一些实现建议包括:

  • 依赖 Spring BeanFactory 的默认行为,即 bean 按注册顺序初始化。您可以通过在 XML 配置中采用一组 <import/> 元素来排列应用模块的常见做法,并确保数据库和数据库初始化列在最前,从而轻松安排这一点。

  • 将 SessionFactory 与使用它的业务组件分开,把它们放入不同的 ApplicationContext 实例中来控制其启动顺序(例如,父上下文包含 SessionFactory,子上下文包含业务组件)。这种结构在 Spring Web 应用中很常见,但也可以更广泛地应用。

  • 使用表和用户定义类型的模式管理,借助 Spring Data Cassandra 内置的模式生成器来初始化键空间。

表与用户定义类型​

Spring Data for Apache Cassandra 通过契合您数据模型的已映射实体类来处理数据访问。您可以使用这些实体类来创建 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.RECREATE 和 SchemaAction.RECREATE_DROP_UNUSED 会删除您的表并丢失所有数据。RECREATE_DROP_UNUSED 还会删除应用未知的表和类型。

为模式管理启用表和用户定义类型​

基于元数据的映射介绍了使用约定和注解的对象映射。为防止不期望的类被创建为表或类型,模式管理仅对标注了 @Table 的实体和标注了 @UserDefinedType 的用户定义类型生效。实体通过扫描 classpath 发现。实体扫描需要一个或多个基础包。使用 TupleValue 的元组类型列不提供任何类型细节。因此,您必须用 @CassandraType(type = TUPLE, typeArguments = …) 标注此类列属性,以指定所需的列类型。

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

示例 5. 指定实体基础包

@Configuration
public class EntityBasePackagesConfiguration extends AbstractCassandraConfiguration {

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

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