跳到主要内容

架构管理

ChatGPT-4o-mini 中英对照 Schema Management

Apache Cassandra 是一个数据存储系统,需要在进行任何数据交互之前定义模式。Spring Data for Apache Cassandra 可以帮助您进行模式创建。

CQL 规范

CQL 规范是一个抽象,用于表示 CQL DDL 操作,如表创建或 keyspace 删除。以下是可用的 CQL 对象类型的规范:

  • 键空间

  • 索引

  • 用户定义类型

备注

Cassandra 的不同版本可以支持物化视图、用户定义的函数、角色以及更多其他对象类型。Spring Data for Apache Cassandra 仅提供上述列出的类型的规范。

这些可以通过流式接口用于创建、修改和删除 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);
source

示例 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);
source

示例 3. 指定 Cassandra 索引

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

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

你可以将规范与配置 API 一起使用,以定义 keyspace 创建和模式操作。

Keyspaces 和生命周期脚本

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

声明一个带有规范的 keyspace 允许创建和删除 Keyspace。它从规范中派生 CQL,因此您无需自己编写 CQL。以下示例使用 XML 指定一个 Cassandra keyspace:

示例 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"));
}

// ...
}
java
<?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>
xml
备注

创建 Keyspace 允许快速引导,无需外部 Keyspace 管理。这在某些场景中非常有用,但应该谨慎使用。在应用程序关闭时删除 Keyspace 会移除 Keyspace 和其中所有表的数据。

初始化 SessionFactory

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

初始化一个 Keyspace

您可以提供任意的 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;"));
}

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

以下示例对指定的 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>
xml
  • 从名为 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;
}

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

在前面的例子中,我们假设有时脚本会在一个空的 keyspace 上运行,并且脚本中包含一些 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;
}

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

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

如果你需要比 XML 命名空间提供的更多控制,可以直接使用 SessionFactoryInitializer 并将其定义为应用程序中的一个组件。

初始化依赖于 Keyspace 的其他组件

一类大型应用程序(那些在 Spring 上下文启动后才使用数据库的应用程序)可以直接使用数据库初始化器,无需进一步的复杂操作。如果您的应用程序不属于这一类,您可能需要阅读本节的其余部分。

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

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

如果应用程序在你的控制之下,改变缓存初始化策略可能很简单,否则就不然。以下是一些实现此策略的建议:

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

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

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

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

  • 依赖于 Spring BeanFactory 的默认行为,即 beans 按照注册顺序初始化。你可以通过采用常见的做法,在 XML 配置中使用一组 <import/> 元素来排列应用程序模块,并确保数据库和数据库初始化首先列出,从而轻松实现这一点。

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

  • 使用 表和用户定义类型 的架构管理,通过 Spring Data Cassandra 的内置架构生成器初始化 keyspace。

表和用户定义类型

Spring Data for Apache Cassandra 通过映射实体类来处理数据访问,这些实体类与您的数据模型相匹配。您可以使用这些实体类来创建 Cassandra 表的规范和用户类型定义。

模式创建与 CqlSession 初始化通过 SchemaAction 关联。支持以下操作:

  • 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 还会删除应用程序未知的表和类型。

启用表和用户定义类型以进行模式管理

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

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

示例 5. 指定实体基础包

@Configuration
public class EntityBasePackagesConfiguration extends AbstractCassandraConfiguration {

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

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