Java 后端手册JAVA BACKEND

CHAPTER 04应用层 · PERSISTENCE

数据持久化

JPA/Hibernate、MyBatis、事务传播、连接池 HikariCP 与迁移。把业务数据可靠地持久化到关系数据库。

开始阅读难度中级先修第三章约3小时
04.1

JPA 实体映射

JPA 用注解把类映射到表:@Entity、@Id、@GeneratedValue、@OneToMany 等。Hibernate 是主流实现。

@Entity
@Table(name = "orders")
public class Order {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private BigDecimal total;
    @Enumerated(EnumType.STRING)
    private OrderStatus status;
}
N+1 查询

关联集合默认懒加载,遍历时每条触发一次查询。用 @EntityGraph 或 join fetch 一次性取出,避免性能灾难。

04.2

Repository

继承 JpaRepository 即可获得 CRUD;方法名自动推导查询,或写 @Query(JPQL/原生 SQL)。

public interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByStatus(OrderStatus status);
    @Query("select o from Order o where o.total > :t")
    List<Order> bigOrders(@Param("t") BigDecimal t);
}
04.3

MyBatis 范式

MyBatis 把 SQL 写在 XML/Mapper 里,对 SQL 掌控更强,适合复杂查询与报表。与 JPA"全自动"互补。

@Mapper
public interface UserMapper {
    @Select("select * from users where id = #{id}")
    User selectById(@Param("id") Long id);
}
选型

CRUD 多、模型驱动选 JPA 提效;SQL 复杂、需精细控制执行计划选 MyBatis。一个项目可两者并存。

04.4

事务基础

@Transactional 声明方法在事务中运行,成功提交、抛异常回滚。默认只在 public 方法且由容器调用时生效。

@Transactional
public void placeOrder(OrderCmd cmd) {
    orderRepo.save(order);
    inventoryRepo.decrease(cmd.getSku(), cmd.getQty());
    // 任一失败整体回滚
}
04.5

事务传播

传播行为决定"被调用方法如何加入现有事务":REQUIRED(加入或新建)、REQUIRES_NEW(挂起开新)、NESTED 等。

传播行为
REQUIRED有则加入,无则新建(最常用)
REQUIRES_NEW挂起当前,开独立新事务
SUPPORTS有则用,无则非事务
自调用失效

同类里方法 A 调 @Transactional 方法 B,事务不生效(代理未介入)。把 B 抽到另一个 Bean 或用 AOP 代理调用。

04.6

连接池 HikariCP

HikariCP 是 Spring Boot 默认连接池,快且稳。合理配置 maximumPoolSize,避免耗尽数据库或被拒绝。

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 3000
04.7

数据库迁移

用 Flyway 或 Liquibase 把 schema 变更纳入版本管理,按序执行、可回滚,避免手工改库带来的环境漂移。

-- V1__create_order.sql
CREATE TABLE orders (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  total DECIMAL(12,2) NOT NULL,
  status VARCHAR(20) NOT NULL
);
大表变更

生产加索引用 CREATE INDEX CONCURRENTLY(PostgreSQL)等在线手段,避免锁表导致长时间不可写。

04.8

常见陷阱与 FAQ

Transactional 标注 private 方法为何不生效?

Spring 事务基于代理,只能拦截 public 且经容器调用的方法。private/内部自调用都不会走代理。

懒加载在 Controller 里报错?

事务在 Service 已提交,游离态实体再访问懒加载关联会抛 LazyInitializationException。用 DTO 投影或 @EntityGraph 在事务内取好数据。

连接池多大合适?

取决于 DB 承载与并发。先小(如 CPU 核数*2+)再依据监控调整,避免超过数据库 max_connections。

04.9

实战练习与资源

延伸资源