JPA 深入与性能优化

知识库
知识库文档
/tech-stacks/spring-data-jpa/tutorial/JPA 深入与性能优化.md

文档

Spring Data JPA 深入与性能优化教程

第一章:实体映射详解

1.1 关联关系

@Entity
public class Order {
    @Id @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)  // 默认 LAZY,建议显式声明
    @JoinColumn(name = "user_id")
    private User user;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItem> items = new ArrayList<>();

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "payment_id", unique = true)
    private Payment payment;
}

@Entity
public class OrderItem {
    @Id @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;

    @ManyToOne(fetch = FetchType.LAZY)
    private Product product;

    private Integer quantity;
}

1.2 FetchType 策略

FetchType 行为 风险
LAZY(推荐) 使用时才加载 需事务内访问,否则 LazyInitializationException
EAGER 立即加载 可能导致 N+1 或 Cartesian 积

1.3 Cascade 级联操作

@OneToMany(mappedBy = "order",
    cascade = {CascadeType.PERSIST, CascadeType.MERGE},
    orphanRemoval = true)
private List<OrderItem> items;

// CascadeType.ALL     = 所有操作级联
// CascadeType.PERSIST = 保存时级联
// CascadeType.MERGE   = 更新时级联
// CascadeType.REMOVE  = 删除时级联
// orphanRemoval=true  = 从集合中移除时自动删除数据库记录

第二章:N+1 问题与解决方案

2.1 问题示例

// 场景:查询 10 个用户及其订单
List<User> users = userRepository.findAll();  // 1 条 SQL: SELECT * FROM users

for (User user : users) {
    // 每个 user.getOrders() 触发 1 条 SQL
    System.out.println(user.getOrders().size());  // 10 条 SQL: SELECT * FROM orders WHERE user_id=?
}
// 总计:11 条 SQL(N+1)

2.2 解决方案

// 方案 1:@EntityGraph
public interface UserRepository extends JpaRepository<User, Long> {
    @EntityGraph(attributePaths = {"orders"})
    List<User> findAll();
}

// 方案 2:JOIN FETCH
@Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();

// 方案 3:@BatchSize
@Entity
public class User {
    @BatchSize(size = 100)
    @OneToMany(mappedBy = "user")
    private List<Order> orders;
}
// 将 N 条 SQL 缩减为 ceil(N/100) 条

第三章:审计与乐观锁

3.1 自动审计

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String lastModifiedBy;
}

@Entity
public class Product extends BaseEntity {
    // 业务字段...
}

// 启用审计:@Configuration + @EnableJpaAuditing
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class JpaConfig {
    @Bean
    public AuditorAware<String> auditorProvider() {
        return () -> Optional.of(SecurityContextHolder.getContext()
            .getAuthentication().getName());
    }
}

3.2 乐观锁

@Entity
public class Product {
    @Version
    private Long version;

    // 更新时 Hibernate 自动:
    // UPDATE ... SET version = version + 1 WHERE id = ? AND version = ?
    // 如果 version 不匹配,抛出 OptimisticLockException
}

第四章:性能最佳实践

4.1 批量操作

// 批量保存(JDBC batch)
spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true

// 大量数据用 JdbcTemplate 而非 JPA
@Autowired
private JdbcTemplate jdbcTemplate;

public void batchInsert(List<Product> products) {
    jdbcTemplate.batchUpdate(
        "INSERT INTO products(name, price, stock) VALUES (?, ?, ?)",
        products, 100,
        (ps, product) -> {
            ps.setString(1, product.getName());
            ps.setDouble(2, product.getPrice());
            ps.setInt(3, product.getStock());
        }
    );
}

4.2 只读优化

@Transactional(readOnly = true)
public List<Product> findAllProducts() {
    return productRepository.findAll();
}
// Hibernate 脏检查被禁用,减少内存占用

4.3 DTO 投影

// 接口投影(推荐)
public interface ProductSummary {
    String getName();
    Double getPrice();
}

// Repository
@Query("SELECT p.name AS name, p.price AS price FROM Product p")
List<ProductSummary> findSummaries();

// 类投影
@Query("SELECT new com.example.dto.ProductDto(p.name, p.price) FROM Product p")
List<ProductDto> findDtos();

思考题

  1. 为什么在多对一关联中推荐使用 FetchType.LAZY
  2. @Transactional(readOnly = true) 在 JPA 中的具体优化是什么?
  3. 如何避免批量处理中的 OOM(内存溢出)?
  4. JPA 的 Persistence Context 和一级缓存有什么关系?

信息

路径
/tech-stacks/spring-data-jpa/tutorial/JPA 深入与性能优化.md
更新时间
2026/5/30