Cassandra 是 Apache 基金会旗下的分布式 NoSQL 数据库,它采用去中心化的集群架构,天然支持多数据中心复制和水平扩展,在海量数据、高写入吞吐的场景下表现出色。Spring Data Cassandra 提供了与 Spring 生态无缝集成的数据访问抽象层,开发者只需要编写少量代码,就能在 Spring Boot 项目中完成对 Cassandra 的增删改查操作。本文将从环境搭建、配置、实体映射、数据操作几个方面,完整演示整合过程。

一、引入依赖与基础配置
首先创建一个 Spring Boot 项目,推荐使用 Spring Initializr 生成骨架,然后引入 Cassandra 相关依赖。核心依赖是 spring-boot-starter-data-cassandra,它会自动带上 Cassandra 驱动和 Spring Data 的相关模块。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
</dependency>依赖引入后,Spring Boot 的自动配置机制会探测 classpath 下的驱动类,并自动创建 CassandraTemplate 和 CqlSession。接下来在 application.yml 中编写连接配置,包括联系点地址、端口、键空间、用户名密码以及本地数据中心名称。
spring:
data:
cassandra:
contact-points: 127.0.0.1:9042
keyspace-name: demo_keyspace
username: cassandra
password: cassandra
local-datacenter: datacenter1
schema-action: create-if-not-exists其中 local-datacenter 是 Cassandra 4.x 驱动强制要求的配置项,缺失会导致连接失败。schema-action 设置为 create-if-not-exists 后,Spring Data 会在启动时根据实体定义自动建表,方便开发阶段快速验证,生产环境建议关闭,改由 migration 脚本管理表结构。
如果键空间还不存在,可以提前通过 CQL 手动创建:CREATE KEYSPACE IF NOT EXISTS demo_keyspace WITH replication = {'class':'SimpleStrategy','replication_factor':1};。本地开发用 SimpleStrategy 即可,生产集群一般使用 NetworkTopologyStrategy 来控制每个数据中心的副本数。
二、实体映射与主键设计
Spring Data Cassandra 使用 @Table 注解将 Java 类映射到 Cassandra 表,这一点与 JPA 的 @Entity 类似,但主键的设计方式差别很大。Cassandra 的主键由分区键和聚类键组成,分区键决定数据存储在集群中的哪个节点,聚类键决定同一分区内数据的排序方式。理解这一点是正确建模的前提,因为 Cassandra 不支持跨分区 join 和任意条件的 where 查询,查询条件必须与主键设计匹配。
下面定义一个用户表实体,使用 @PrimaryKeyClass 组合主键来演示分区键与聚类键的用法。
// 组合主键类:分区键决定数据落在哪个节点
@PrimaryKeyClass
public class OrderKey implements Serializable {
@PrimaryKeyColumn(name = "user_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private Long userId;
@PrimaryKeyColumn(name = "order_time", ordinal = 1, type = PrimaryKeyType.CLUSTERED,
ordering = Ordering.DESCENDING)
private LocalDateTime orderTime;
// 省略 getter 和 setter
}
@Table("orders")
public class Order {
@PrimaryKey
private OrderKey id;
private String productName;
private BigDecimal amount;
private String status;
// 省略 getter 和 setter
}上面的设计中,user_id 是分区键,同一个用户的所有订单会落在同一个分区里,按 order_time 倒序排列。这样设计之后,查询某用户最近的订单会非常高效。反过来,如果想按产品维度统计订单,就无法直接支持,需要另建一张以产品为分区键的反范式表。Cassandra 的建模原则是围绕查询来设计表,一张表服务一类查询,这与关系型数据库先建模后写查询的思路正好相反。
字段类型上需要注意,Java 8 的日期时间类型如 LocalDateTime 会被映射为 Cassandra 的 timestamp 类型,BigDecimal 映射为 decimal。如果实体中的类型无法自动映射,可以通过 @CassandraType 注解显式指定 CQL 类型,避免启动时报错。
三、使用 CassandraRepository 进行数据操作
Spring Data Cassandra 提供了 CassandraRepository 接口,用法与 JPA 的 JpaRepository 高度相似。只需要声明接口方法,框架会在运行时自动生成实现。
public interface OrderRepository extends CassandraRepository<Order, OrderKey> {
// 查询条件必须包含完整分区键
List<Order> findByIdUserId(Long userId);
// 分区键 + 范围条件,利用聚类键排序特性
List<Order> findByIdUserIdAndIdOrderTimeBetween(Long userId,
LocalDateTime start, LocalDateTime end);
}方法名派生查询的规则与 JPA 一致,但有一个关键限制:查询条件必须从分区键开始,不能跳过分区键直接按普通字段过滤,否则启动时校验阶段就会抛出异常。这个限制经常让习惯关系型数据库的开发者感到不适应,但它本质上是 Cassandra 分布式架构决定的——没有分区键,驱动不知道去请求哪个节点。
然后编写一个 Service 演示完整的增删改查流程。
@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public Order createOrder(Long userId, String productName, BigDecimal amount) {
Order order = new Order();
OrderKey key = new OrderKey();
key.setUserId(userId);
key.setOrderTime(LocalDateTime.now());
order.setId(key);
order.setProductName(productName);
order.setAmount(amount);
order.setStatus("CREATED");
return orderRepository.save(order);
}
public List<Order> getUserOrders(Long userId) {
return orderRepository.findByIdUserId(userId);
}
public void deleteOrder(Long userId, LocalDateTime orderTime) {
OrderKey key = new OrderKey();
key.setUserId(userId);
key.setOrderTime(orderTime);
orderRepository.deleteById(key);
}
}除了 Repository,也可以直接注入 CassandraTemplate 执行更灵活的操作,例如自定义 CQL 语句或批量写入。 CassandraTemplate 提供了 insert、select、update 等方法,同时支持通过 CqlSession 执行原生 CQL,适合处理 Repository 派生查询无法表达的复杂场景。
@Autowired
private CassandraTemplate cassandraTemplate;
public List<Order> findCreatedOrders(Long userId) {
return cassandraTemplate.select(
Query.empty()
.columns(Columns.from("user_id", "product_name", "status"))
.where(Relation.column("user_id").isEqualTo(literal(userId)))
.withAllowFiltering(),
Order.class);
}上面的 withAllowFiltering 对应 CQL 中的 ALLOW FILTERING,它允许对普通字段做过滤,但代价是全分区扫描,性能很差,只适合开发调试或数据量极小的表,生产环境务必慎用。
四、常见坑与最佳实践
整合过程中有几个高频问题值得注意。第一,Cassandra 对写入是追加式的,save 一条已存在的主键数据等于覆盖更新,且整个分区键对应的行会被新值替换,未赋值的集合字段可能被清空,更新时要传入完整对象。第二,Cassandra 没有事务和跨行一致性,不要尝试用注解或代码模拟分布式事务,需求上有强事务的部分应留在关系型数据库中。
第三,批量写入时建议使用驱动的异步 API 或 BatchStatement,但批量语句只应作用于同一个分区,跨分区批量不仅没有性能收益,反而会给协调节点带来压力。第四,连接超时和读写超时可以通过配置调整,例如在 yml 中设置 request.timeout 和 connection.connect-timeout,集群规模大或网络抖动的环境下尤其重要。
最后在架构层面,如果业务需要二级索引查询、全文检索或聚合统计,Cassandra 本身并不擅长,通常的做法是搭配 Elasticsearch 做检索、配合 Spark 或 Flink 做离线分析,让 Cassandra 专注于高吞吐的在线写入和基于主键的快速读取。按照这套思路组织系统,才能真正发挥 Cassandra 作为 NoSQL 存储的价值。
Spring BootCassandraNoSQL修改时间:2026-09-09 16:51:09