模式管理
Apache Cassandra 是一种数据存储系统,在进行任何数据交互之前都需要先定义模式。 Spring Data for Apache Cassandra 可以帮助您完成模式创建。
CQL 规范
CQL 规范是一种抽象表示,用于描述 CQL DDL 操作,例如创建表或删除 keyspace。 规范适用于以下 CQL 对象类型:
-
键空间
-
表格
-
索引
-
用户定义的类型
| Cassandra 的某些变体可以支持物化视图、用户自定义函数、角色以及更多类型的对象。 Spring Data for Apache Cassandra 仅为上述列出的类型提供规范说明。 |
这些可用于通过流畅的接口来创建(CREATE)、修改(ALTER)和删除(DROP)CQL 对象。SpecificationBuilder 是构建此类规范的入口点。
随后,您可以使用 CqlGenerator.toCql(…) 轻松地从规范生成 CQL。
请参见以下示例,以创建用于键空间(keyspace)创建、表(table)创建和索引(index)创建的规范。
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);
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);
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 操作。
键空间与生命周期脚本
首先需要创建的是一个 Cassandra keyspace(键空间)。
Keyspace 是一种逻辑分组,用于将具有相同复制因子和复制策略的表组织在一起。
Keyspace 管理位于 CqlSession 配置中,该配置包含 KeyspaceSpecification 以及启动和关闭时执行的 CQL 脚本。
通过规范声明 keyspace 可以实现 keyspace 的创建和删除。 它会根据该规范自动生成 CQL,因此你无需手动编写 CQL。 以下示例使用 XML 来指定一个 Cassandra keyspace:
@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>
| Keyspace 创建允许快速引导启动,而无需依赖外部的 keyspace 管理。 这在某些场景下可能很有用,但应谨慎使用。 在应用程序关闭时删除 keyspace 会移除该 keyspace 及其所有表中的数据。 |
初始化SessionFactory
org.springframework.data.cassandra.core.cql.session.init 包提供了对初始化现有 SessionFactory 的支持。
有时您可能需要初始化某个服务器上运行的 keyspace。
初始化 Keyspace
您可以提供任意的 CQL 语句,这些语句将在配置的 keyspace 中于 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>
前面的示例针对指定的 keyspace 运行两个脚本。
第一个脚本用于创建 schema,第二个脚本则使用测试数据集填充表。
脚本位置也可以采用 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 | 从名为 enabled 的系统属性中获取 INITIALIZE_KEYSPACE 的值。 |
控制现有数据处理方式的第二种选项是对失败采取更宽容的态度。 为此,您可以控制初始化器在执行脚本中的 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>
在前面的示例中,我们指出有时脚本会针对一个空的 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;
}
// ...
}
<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 方法,或实现了 afterPropertiesSet() 接口的组件中的 InitializingBean 方法)。
如果其他 Bean 依赖于相同的数据源并在其初始化回调中使用该 SessionFactory,则可能会出现问题,因为此时数据尚未完成初始化。
一个常见的例子是:缓存在应用启动时就急切地初始化,并从数据库中加载数据。
要解决此问题,您有两个选项:将缓存初始化策略更改为稍后的阶段,或者确保 keyspace 初始化器首先被初始化。
如果你能控制该应用程序,更改缓存初始化策略可能会很容易;否则则不然。 关于如何实现这一点的一些建议包括:
-
使缓存在首次使用时延迟初始化,从而提升应用程序的启动时间。
-
让你的缓存或负责初始化缓存的独立组件实现
Lifecycle或SmartLifecycle接口。 当应用上下文启动时,你可以通过设置SmartLifecycle的autoStartup标志来自动启动它;也可以通过对所属上下文调用Lifecycle方法来手动启动一个ConfigurableApplicationContext.start()。 -
使用 Spring 的
ApplicationEvent或类似的自定义观察者机制来触发缓存的初始化。ContextRefreshedEvent在上下文准备就绪(所有 Bean 均已初始化完毕)时总会由上下文发布,因此这通常是一个有用的钩子(SmartLifecycle默认就是通过这种方式工作的)。
确保键空间初始化器首先被初始化也很简单。 关于如何实现这一点的一些建议包括:
-
依赖 Spring
BeanFactory的默认行为,即 Bean 按照注册顺序进行初始化。 你可以通过在 XML 配置中采用一组<import/>元素的常见做法来轻松实现这一点,这些元素用于对应用程序模块进行排序,并确保数据库及其初始化配置被列在最前面。 -
通过将
SessionFactory与其使用的业务组件分别置于独立的ApplicationContext实例中(例如,父上下文包含SessionFactory,子上下文包含业务组件),来分离3和业务组件,并控制它们的启动顺序。 这种结构在Spring Web应用程序中很常见,但也可以更广泛地应用。 -
使用表和用户自定义类型的模式管理功能,通过 Spring Data Cassandra 内置的模式生成器来初始化 keyspace。
表和自定义类型
Spring Data for Apache Cassandra 通过映射的实体类来实现数据访问,这些实体类与您的数据模型相匹配。 您可以使用这些实体类来创建 Cassandra 表结构定义和用户自定义类型(User Type)定义。
模式创建通过 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.RECREATE 和 SchemaAction.RECREATE_DROP_UNUSED 会删除您的表并丢失所有数据。
RECREATE_DROP_UNUSED 还会删除应用程序未知的表和类型。 |
启用表和自定义类型以进行架构管理
基于元数据的映射介绍了使用约定和注解进行对象映射的方法。
为防止不需要的类被创建为表或类型,仅对标注了 @Table 的实体以及标注了 @UserDefinedType 的用户自定义类型启用模式管理。
实体通过扫描类路径(classpath)来发现。
实体扫描需要指定一个或多个基础包。
使用 TupleValue 的元组类型列不提供任何类型细节。
因此,您必须为此类列属性添加 @CassandraType(type = TUPLE, typeArguments = …) 注解,
以指定所需的列类型。
以下示例展示了如何在 XML 配置中指定实体基础包:
@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>