Spring Data JDBC
概述
Spring Data JDBC 是 Spring Data 家族中一个「特立独行」的成员——它不是 JPA,没有 EntityManager、没有一级缓存、没有脏检查,也没有懒加载。
设计哲学
| 对比维度 | JPA / Hibernate | Spring Data JDBC |
|---|---|---|
| 底层 ORM | Hibernate(全自动) | 直接 JDBC(无 Session) |
| 缓存 | 一级/二级缓存 | 无缓存 |
| 懒加载 | 支持 | 不支持(加载即全部加载) |
| 脏检查 | 自动 | 无(需要显式 save) |
| Aggregate 支持 | 间接(@OneToMany + Cascade) | 原生(Aggregate 概念内置) |
Spring Data JDBC 的设计目标:用最简单的方式做 80% 的 CRUD,适合 DDD 聚合根场景。
一、核心概念
1.1 Aggregate 设计
Spring Data JDBC 以 DDD 聚合(Aggregate) 为核心:
- 一个 Aggregate 由一个 Aggregate Root(聚合根)和多个 Entity(实体)组成
- Aggregate 内的对象通过数据库外键关联
- 加载 Aggregate Root 时,关联的实体一并加载
java
@Table("t_order")
public class Order {
@Id
private Long id;
private String orderNo;
private BigDecimal totalAmount;
private OrderStatus status;
// 聚合内的实体 — 所有 OrderItem 随 Order 一起加载
private List<OrderItem> items;
// 值对象 — 直接内联到表中
private Address shippingAddress;
}java
public class OrderItem {
private Long id;
private Long productId;
private String productName;
private Integer quantity;
private BigDecimal price;
}1.2 Repository 定义
java
public interface OrderRepository extends CrudRepository<Order, Long> {
List<Order> findByStatus(OrderStatus status);
Optional<Order> findByOrderNo(String orderNo);
}二、数据库映射
2.1 表命名策略
默认策略:Order → order 表,OrderItem → order_item 表(聚合根类名 + _ + 实体类名)。
可通过 @Table / @Column 自定义:
java
@Table("CUSTOM_ORDER_TABLE")
public class Order {
@Column("ORDER_NUMBER")
private String orderNo;
}2.2 引用(Reference) vs 嵌入(Embedded)
java
// 1️⃣ 一对一嵌入 — 值对象内联到同一张表
public class Address {
private String province;
private String city;
private String detail;
}
// 2️⃣ 一对多引用 — 自动创建关联表
public class Order {
private List<OrderItem> items; // items 存储在 order_item 表,通过 order 外键关联
}
// 3️⃣ 多对多引用 — 自动创建中间表
public class Product {
private Set<Category> categories;
}| 关系 | 注解 | 存储方式 |
|---|---|---|
| 值对象嵌入 | 无需注解 | 内联到同一张表的列 |
| 1:1 引用 | @MappedCollection(idColumn = "order_id") | 目标表加外键列 |
| 1:N 引用 | List<T> / Set<T> | 目标表加外键列 |
| N:M 引用 | Set<T> + 双方引用 | 自动创建中间表 |
2.3 自定义 Converter
java
@WritingConverter
public class OrderStatusWriteConverter implements Converter<OrderStatus, Integer> {
@Override
public Integer convert(OrderStatus source) {
return source.getCode();
}
}
@ReadingConverter
public class OrderStatusReadConverter implements Converter<Integer, OrderStatus> {
@Override
public OrderStatus convert(Integer source) {
return OrderStatus.of(source);
}
}注册 Converter:
java
@Bean
public DataJdbcCustomConversions customConversions() {
return new DataJdbcCustomConversions(Arrays.asList(
new OrderStatusWriteConverter(),
new OrderStatusReadConverter()
));
}三、事件回调
Spring Data JDBC 提供 4 种生命周期事件:
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
BeforeSaveEvent | 保存前 | 补充审计字段 |
AfterSaveEvent | 保存后 | 发送领域事件 |
BeforeDeleteEvent | 删除前 | 检查删除权限 |
AfterDeleteEvent | 删除后 | 清理缓存 |
AfterLoadEvent | 加载后 | 数据脱敏 |
java
@Component
public class OrderAuditHandler {
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleBeforeSave(BeforeSaveEvent<Order> event) {
Order order = event.getEntity();
if (order.getId() == null) {
order.setCreateTime(LocalDateTime.now());
}
order.setUpdateTime(LocalDateTime.now());
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleAfterSave(AfterSaveEvent<Order> event) {
// 发送订单创建领域事件
eventPublisher.publish(new OrderCreatedEvent(event.getEntity()));
}
}四、实战:DDD 聚合 Repository
4.1 场景:电商订单聚合
java
// 聚合根
@Table("t_order")
public class Order {
@Id
private Long id;
private String orderNo;
private Long userId;
private BigDecimal totalAmount;
private OrderStatus status;
private LocalDateTime createTime;
// 值对象
private Address shippingAddress;
// 聚合内实体
@MappedCollection(idColumn = "order_id")
private List<OrderItem> items;
// 领域方法
public void addItem(Product product, int quantity) {
OrderItem item = new OrderItem();
item.setProductId(product.getId());
item.setProductName(product.getName());
item.setPrice(product.getPrice());
item.setQuantity(quantity);
items.add(item);
recalculateTotal();
}
private void recalculateTotal() {
this.totalAmount = items.stream()
.map(i -> i.getPrice().multiply(BigDecimal.valueOf(i.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
public void pay() {
if (this.status != OrderStatus.PENDING) {
throw new IllegalStateException("订单状态不允许支付");
}
this.status = OrderStatus.PAID;
}
}java
// 聚合实体
public class OrderItem {
private Long id;
private Long productId;
private String productName;
private BigDecimal price;
private Integer quantity;
}java
// 值对象
public class Address {
private final String province;
private final String city;
private final String detail;
public Address(String province, String city, String detail) {
this.province = province;
this.city = city;
this.detail = detail;
}
// getters...
}4.2 Repository + Service
java
public interface OrderRepository extends CrudRepository<Order, Long> {
Optional<Order> findByOrderNo(String orderNo);
List<Order> findByUserIdOrderByCreateTimeDesc(Long userId);
}
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final ApplicationEventPublisher eventPublisher;
@Transactional
public Order createOrder(Long userId, List<CartItem> cart) {
Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setUserId(userId);
order.setStatus(OrderStatus.PENDING);
order.setItems(new ArrayList<>());
for (CartItem item : cart) {
order.addItem(item.getProduct(), item.getQuantity());
}
Order saved = orderRepository.save(order);
eventPublisher.publishEvent(new OrderCreatedEvent(saved));
return saved;
}
@Transactional(readOnly = true)
public Order findOrder(String orderNo) {
return orderRepository.findByOrderNo(orderNo)
.orElseThrow(() -> new OrderNotFoundException(orderNo));
}
}五、Spring Data JDBC vs JPA 选型对比
| 维度 | JPA / Hibernate | Spring Data JDBC |
|---|---|---|
| 学习曲线 | 陡峭(Persistence Context、懒加载、级联、N+1) | 平缓(无状态,save 即执行) |
| SQL 控制 | 自动生成,HQL/JPQL 调试不易 | SQL 可预测——你知道最终会执行什么 SQL |
| 性能 | 一级缓存/批量操作优化好 | 无缓存,但 SQL 简单,无「幻读」风险 |
| DDD 适配 | 需要额外设计 Aggregate 边界 | 原生支持 Aggregate |
| 复杂查询 | JPQL / Criteria / QueryDSL 强大 | 需要 @Query 手动写 SQL |
| 多表关联 | 懒加载 + Fetch 策略配置丰富 | 加载 Aggregate 时一次性全加载 |
| 事务 | 共享 Persistence Context | 无共享状态,更安全 |
选型决策树
需要复杂查询 + 懒加载 + 缓存?
├── 是 → JPA / Hibernate
└── 否 → 是否使用 DDD Aggregate?
├── 是 → Spring Data JDBC
└── 否 → 是否只要简单 CRUD?
├── 是 → Spring Data JDBC
└── 否 → JPA六、总结
| 知识点 | 要点 |
|---|---|
| Aggregate 设计 | 聚合根 + 实体 + 值对象,一起加载一起保存 |
| Repository | CrudRepository<P, ID>,返回 Optional |
| 映射 | @Table / @Column / @MappedCollection |
| Converter | @ReadingConverter / @WritingConverter 定义类型转换 |
| 事件 | BeforeSave / AfterSave / AfterLoad / BeforeDelete |
| 与 JPA 对比 | JDBC 无缓存/无懒加载/无脏检查,但更简单可预测 |
参考链接: