Schema Management
Apache Cassandra 是一个在任何数据交互之前需要模式定义的数据存储。Spring Data for Apache Cassandra 可以用模式创建来支持你。
Keyspaces and Lifecycle Scripts
首先从一个 Cassandra 键空间开始。键空间是共享相同副本因子和副本策略的表的逻辑分组。键空间管理位于 CqlSession
配置中,其中有 KeyspaceSpecification
以及启动和关闭 CQL 脚本执行。
使用规范声明一个键空间允许创建和删除 Keyspace。它从规范中获取 CQL,这样你就不用自己编写 CQL。以下示例使用 XML 指定了一个 Cassandra 键空间:
/*
* Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.example;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.core.cql.keyspace.CreateKeyspaceSpecification;
import org.springframework.data.cassandra.core.cql.keyspace.DataCenterReplication;
import org.springframework.data.cassandra.core.cql.keyspace.DropKeyspaceSpecification;
import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceOption;
// tag::class[]
@Configuration
public class CreateKeyspaceConfiguration extends AbstractCassandraConfiguration implements BeanClassLoaderAware {
@Override
protected List<CreateKeyspaceSpecification> getKeyspaceCreations() {
CreateKeyspaceSpecification specification = CreateKeyspaceSpecification.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"));
}
// ...
// end::class[]
@Override
protected String getKeyspaceName() {
return null;
}
// tag::class[]
}
// end::class[]
<?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:
/*
* Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.example;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.core.cql.session.init.KeyspacePopulator;
import org.springframework.data.cassandra.core.cql.session.init.ResourceKeyspacePopulator;
import org.springframework.lang.Nullable;
// tag::class[]
@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;"));
}
// ...
// end::class[]
@Override
protected String getKeyspaceName() {
return null;
}
// tag::class[]
}
// end::class[]
<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 中的某些错误的能力,如下例所示:
/*
* Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.example;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.core.cql.session.init.KeyspacePopulator;
import org.springframework.data.cassandra.core.cql.session.init.ResourceKeyspacePopulator;
import org.springframework.lang.Nullable;
// tag::class[]
@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;
}
// ...
// end::class[]
@Override
protected String getKeyspaceName() {
return null;
}
// tag::class[]
}
// end::class[]
<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
(忽略所有失败)。
如果脚本中根本不包含 ;
字符,那么每条语句都应该使用 ;
或一个新建行进行分隔。你可以进行全局控制或逐个脚本控制,如下例所示:
/*
* Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.cassandra.SessionFactory;
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.core.cql.session.init.CompositeKeyspacePopulator;
import org.springframework.data.cassandra.core.cql.session.init.ResourceKeyspacePopulator;
import org.springframework.data.cassandra.core.cql.session.init.SessionFactoryInitializer;
// tag::class[]
@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;
}
// ...
// end::class[]
@Override
protected String getKeyspaceName() {
return null;
}
// tag::class[]
}
// end::class[]
<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 依赖于相同的数据源并使用初始化回调中的会话工厂,则可能会出问题,因为数据尚未初始化。一个常见的例子是一个在应用程序启动时立即初始化并从数据库加载数据的缓存。
为了解决这个问题,你有两个选择:将缓存初始化策略更改为较晚的阶段,或确保键空间初始化器首先初始化。
如果你控制应用程序并且没有其他方式,则更改缓存初始化策略可能会很容易。以下列出了如何实现此目的的一些建议:
-
使高速缓存延迟在首次使用时初始化,这可以改善应用程序的启动时间。
-
让高速缓存或初始化高速缓存的单独组件实现
Lifecycle
或SmartLifecycle
。当应用程序上下文启动时,您可以通过设置其autoStartup
标志自动启动SmartLifecycle
,并且可以通过调用外围上下文上的ConfigurableApplicationContext.start()
来手动启动Lifecycle
。 -
使用 Spring
ApplicationEvent
或类似的自定义观察器机制来触发高速缓存初始化。ContextRefreshedEvent
始终由上下文在它可以随时使用时(在所有 Bean 初始化之后)发布,因此它经常是一个有用的钩子(这是SmartLifecycle
的默认工作方式)。
确保密钥空间初始化程序首先进行初始化也很容易。有关如何实现此操作的一些建议包括:
-
依赖于 Spring
BeanFactory
的默认行为,它在于 Bean 以注册顺序初始化。通过采用 XML 配置中一组<import/>
元素的通用实践来对您的应用程序模块进行排序,并确保数据库和数据库初始列首先列出,可以轻松排列它。 -
将
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
: 删除所有表和类型,并只重新创建已知的表和类型。
|
Enabling Tables and User-Defined Types for Schema Management
Metadata-based Mapping解释了具有约定和注释的对象映射。为防止创建不需要的类作为表或类型,架构管理仅对使用 `@Table`注释的实体和使用 `@UserDefinedType`注释的用户定义类型处于活动状态。实体是通过扫描类路径发现的。实体扫描需要一个或多个基本包。使用 `TupleValue`的元组类型列不提供任何类型化详细信息。因此,您必须使用 `@CassandraType(type = TUPLE, typeArguments = …)`注释此类列属性以指定所需的列类型。
以下示例展示如何在 XML 配置中指定实体基本包:
/*
* Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.example;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
// tag::class[]
@Configuration
public class EntityBasePackagesConfiguration extends AbstractCassandraConfiguration {
@Override
public String[] getEntityBasePackages() {
return new String[] { "com.foo", "com.bar" };
}
// ...
// end::class[]
@Override
protected String getKeyspaceName() {
return null;
}
// tag::class[]
}
// end::class[]
<?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>