37-Spring Data JPA详解

3 阅读18分钟

Spring Data JPA详解

本章导读

Spring Data JPA 基于 JPA 规范,通过 Repository 抽象实现了"零代码"数据访问,极大简化了 CRUD 操作。本章系统讲解 JPA 的实体映射、Repository 接口、JPQL 查询和 Specification 动态查询,帮助你掌握 ORM 框架的核心思想和最佳实践。

学习目标

  • 目标1:掌握 JPA 核心注解和实体关联映射的正确配置
  • 目标2:熟练使用 Repository 方法命名查询和 @Query 注解
  • 目标3:理解 Specification 动态查询原理,解决 N+1 问题并优化性能

前置知识:JDBC 基础、SQL 语法、面向对象思想

阅读时长:约 45 分钟

一、知识概述

Spring Data JPA 是 Spring Data 家族的重要组成部分,它基于 JPA(Java Persistence API)规范,通过极其简化的编程模型,大大减少了数据访问层的样板代码。Spring Data JPA 的核心理念是"Repository 抽象",开发者只需定义接口,框架自动提供实现,实现"零代码"数据访问。

本文将深入讲解 Spring Data JPA 的核心概念、实体映射、Repository 接口、JPQL 查询、Specification 动态查询等内容,帮助你掌握这个强大的 ORM 框架。

二、快速入门

2.1 引入依赖

<!-- Spring Boot 项目引入 Spring Data JPA -->
<dependencies>
    <!-- Spring Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- 数据库驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
    
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

2.2 配置文件

# application.yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/jpa_demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    
    # HikariCP 连接池配置
    hikari:
      minimum-idle: 5
      maximum-pool-size: 20
      idle-timeout: 30000
      max-lifetime: 120000
      connection-timeout: 30000
      
  # JPA 配置
  jpa:
    # 数据库类型
    database: mysql
    # 显示 SQL
    show-sql: true
    # 格式化 SQL
    properties:
      hibernate:
        format_sql: true
        # 二级缓存
        cache:
          use_second_level_cache: false
        # 统计信息
        generate_statistics: true
    # DDL 自动生成策略
    hibernate:
      ddl-auto: update
      # 可选值:
      # - none: 不做任何操作
      # - validate: 验证表结构,不匹配则报错
      # - update: 更新表结构(不会删除已存在的列)
      # - create: 每次启动创建表(会删除旧表)
      # - create-drop: 启动创建,关闭删除
      
    # Open Session In View(生产环境建议关闭)
    open-in-view: false

2.3 快速使用

/**
 * Spring Data JPA 快速入门
 */
@SpringBootApplication
@EnableJpaRepositories(basePackages = "com.example.repository")
public class JpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(JpaApplication.class, args);
    }
}

/**
 * 实体类
 */
@Entity
@Table(name = "sys_user")
@Data
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "username", length = 50, nullable = false, unique = true)
    private String username;
    
    @Column(name = "email", length = 100)
    private String email;
    
    @Column(name = "age")
    private Integer age;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "status", length = 20)
    private UserStatus status = UserStatus.ACTIVE;
    
    @Column(name = "create_time", updatable = false)
    private LocalDateTime createTime;
    
    @Column(name = "update_time")
    private LocalDateTime updateTime;
    
    // 生命周期回调
    @PrePersist
    public void prePersist() {
        this.createTime = LocalDateTime.now();
        this.updateTime = LocalDateTime.now();
    }
    
    @PreUpdate
    public void preUpdate() {
        this.updateTime = LocalDateTime.now();
    }
}

/**
 * 用户状态枚举
 */
public enum UserStatus {
    ACTIVE, INACTIVE, DELETED
}

/**
 * Repository 接口 - 继承 JpaRepository 即可获得 CRUD 能力
 */
public interface UserRepository extends JpaRepository<User, Long> {
    // JpaRepository 已提供:
    // - save(S entity) / saveAll(Iterable<S> entities)
    // - findById(ID id) / findAll() / findAllById(Iterable<ID> ids)
    // - count() / existsById(ID id)
    // - deleteById(ID id) / delete(T entity) / deleteAll()
    // - findAll(Sort sort) / findAll(Pageable pageable)
    
    // 自定义查询方法
    User findByUsername(String username);
    
    List<User> findByStatus(UserStatus status);
    
    List<User> findByAgeBetween(Integer min, Integer max);
    
    @Query("SELECT u FROM User u WHERE u.email LIKE %:domain")
    List<User> findByEmailDomain(@Param("domain") String domain);
}

/**
 * Service 层
 */
@Service
@Transactional
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public User create(User user) {
        return userRepository.save(user);
    }
    
    public User update(User user) {
        return userRepository.save(user);
    }
    
    public void delete(Long id) {
        userRepository.deleteById(id);
    }
    
    public User getById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new EntityNotFoundException("User not found"));
    }
    
    public List<User> listAll() {
        return userRepository.findAll();
    }
    
    public Page<User> listPage(int page, int size) {
        return userRepository.findAll(PageRequest.of(page, size));
    }
}

/**
 * 测试使用
 */
@SpringBootTest
public class QuickStartTest {
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    public void testInsert() {
        User user = new User();
        user.setUsername("zhangsan");
        user.setEmail("zhangsan@example.com");
        user.setAge(25);
        user.setStatus(UserStatus.ACTIVE);
        
        User saved = userRepository.save(user);
        System.out.println("保存成功: " + saved);
    }
    
    @Test
    public void testSelect() {
        // 根据 ID 查询
        Optional<User> optional = userRepository.findById(1L);
        optional.ifPresent(System.out::println);
        
        // 查询所有
        List<User> users = userRepository.findAll();
        users.forEach(System.out::println);
        
        // 分页查询
        Page<User> page = userRepository.findAll(PageRequest.of(0, 10));
        System.out.println("总记录数: " + page.getTotalElements());
        System.out.println("总页数: " + page.getTotalPages());
        System.out.println("当前页数据: " + page.getContent());
    }
    
    @Test
    public void testUpdate() {
        User user = userRepository.findById(1L).orElse(null);
        if (user != null) {
            user.setAge(26);
            userRepository.save(user);
        }
    }
    
    @Test
    public void testDelete() {
        userRepository.deleteById(1L);
    }
}

三、实体映射详解

3.1 基础注解

/**
 * JPA 实体映射基础注解
 */
@Entity
@Table(name = "sys_user", 
       schema = "mydb",
       indexes = {
           @Index(name = "idx_username", columnList = "username", unique = true),
           @Index(name = "idx_create_time", columnList = "create_time")
       },
       uniqueConstraints = {
           @UniqueConstraint(name = "uk_email", columnNames = {"email"})
       })
@Data
public class User {
    
    /**
     * @Id - 主键
     * @GeneratedValue - 主键生成策略
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    // GenerationType 可选值:
    // - IDENTITY: 数据库自增(MySQL)
    // - SEQUENCE: 序列(Oracle)
    // - TABLE: 使用单独的表生成主键
    // - AUTO: JPA 自动选择(默认)
    private Long id;
    
    /**
     * @Column - 字段映射
     */
    @Column(name = "username",      // 数据库列名
            length = 50,            // 长度
            nullable = false,       // 是否允许 null
            unique = true,          // 是否唯一
            insertable = true,      // 是否参与 insert
            updatable = true,       // 是否参与 update
            columnDefinition = "VARCHAR(50) COMMENT '用户名'")  // DDL 定义
    private String username;
    
    /**
     * @Enumerated - 枚举映射
     */
    @Enumerated(EnumType.STRING)  // 存储枚举名称
    // EnumType.ORDINAL: 存储枚举序号(默认,不推荐)
    // EnumType.STRING: 存储枚举名称
    @Column(name = "status")
    private UserStatus status;
    
    /**
     * @Temporal - 日期时间映射
     */
    @Temporal(TemporalType.TIMESTAMP)
    // TemporalType.DATE: 只存日期
    // TemporalType.TIME: 只存时间
    // TemporalType.TIMESTAMP: 日期时间
    @Column(name = "create_time")
    private Date createTime;
    
    /**
     * @Lob - 大对象
     */
    @Lob
    @Column(name = "avatar", columnDefinition = "LONGBLOB")
    private byte[] avatar;
    
    @Lob
    @Column(name = "description", columnDefinition = "LONGTEXT")
    private String description;
    
    /**
     * @Transient - 忽略映射
     */
    @Transient
    private String extraField;
    
    /**
     * @Basic - 基本配置
     */
    @Basic(fetch = FetchType.LAZY)  // 延迟加载
    @Column(name = "large_content")
    private String largeContent;
}

/**
 * 复合主键
 */
@Data
@Embeddable
public class OrderItemId implements Serializable {
    
    @Column(name = "order_id")
    private Long orderId;
    
    @Column(name = "product_id")
    private Long productId;
}

@Entity
@Table(name = "order_item")
@Data
public class OrderItem {
    
    @EmbeddedId
    private OrderItemId id;
    
    @Column(name = "quantity")
    private Integer quantity;
    
    @Column(name = "price")
    private BigDecimal price;
}

// 另一种方式:@IdClass
@Entity
@Table(name = "order_item2")
@IdClass(OrderItemId.class)
@Data
public class OrderItem2 {
    
    @Id
    @Column(name = "order_id")
    private Long orderId;
    
    @Id
    @Column(name = "product_id")
    private Long productId;
    
    @Column(name = "quantity")
    private Integer quantity;
}

3.2 关联映射

/**
 * 一对一关联
 */
@Entity
@Table(name = "user")
@Data
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    /**
     * 一对一 - 主表方
     */
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "profile_id", referencedColumnName = "id")
    // name: 外键列名(在当前表)
    // referencedColumnName: 关联列名(在关联表)
    private UserProfile profile;
}

@Entity
@Table(name = "user_profile")
@Data
public class UserProfile {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String realName;
    
    private String phone;
    
    /**
     * 一对一 - 从表方(可选)
     */
    @OneToOne(mappedBy = "profile", fetch = FetchType.LAZY)
    private User user;
}

/**
 * 一对多关联
 */
@Entity
@Table(name = "dept")
@Data
public class Department {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    /**
     * 一对多 - 部门方
     */
    @OneToMany(mappedBy = "department", 
               fetch = FetchType.LAZY,
               cascade = CascadeType.ALL,
               orphanRemoval = true)  // 孤儿删除
    private List<User> users = new ArrayList<>();
}

@Entity
@Table(name = "user")
@Data
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    /**
     * 多对一 - 用户方
     */
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "dept_id")
    private Department department;
}

/**
 * 多对多关联
 */
@Entity
@Table(name = "user")
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @EqualsAndHashCode.Include
    private Long id;
    
    private String username;
    
    /**
     * 多对多 - User 方(维护方)
     */
    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    @JoinTable(name = "user_role",
               joinColumns = @JoinColumn(name = "user_id"),
               inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles = new HashSet<>();
}

@Entity
@Table(name = "role")
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Role {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @EqualsAndHashCode.Include
    private Long id;
    
    private String name;
    
    /**
     * 多对多 - Role 方(被维护方)
     */
    @ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
    private Set<User> users = new HashSet<>();
}

/**
 * 关联映射最佳实践
 */
@SpringBootTest
public class AssociationMappingTest {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private DepartmentRepository departmentRepository;
    
    /**
     * 级联操作
     */
    @Test
    public void testCascade() {
        // 创建部门
        Department dept = new Department();
        dept.setName("技术部");
        
        // 创建用户
        User user1 = new User();
        user1.setUsername("张三");
        user1.setDepartment(dept);
        
        User user2 = new User();
        user2.setUsername("李四");
        user2.setDepartment(dept);
        
        dept.setUsers(Arrays.asList(user1, user2));
        
        // 保存部门(级联保存用户)
        departmentRepository.save(dept);
    }
    
    /**
     * 多对多操作
     */
    @Test
    public void testManyToMany() {
        User user = userRepository.findById(1L).orElse(null);
        Role role = new Role();
        role.setId(1L);  // 假设角色已存在
        
        // 添加角色
        user.getRoles().add(role);
        userRepository.save(user);
        
        // 移除角色
        user.getRoles().remove(role);
        userRepository.save(user);
    }
    
    /**
     * N+1 问题与解决
     */
    // 问题代码
    @Test
    public void testNPlusOneProblem() {
        List<User> users = userRepository.findAll();
        for (User user : users) {
            // 每次 getDepartment() 都会发起一次查询
            System.out.println(user.getDepartment().getName());
        }
    }
    
    // 解决方案1:JOIN FETCH
    @Query("SELECT u FROM User u JOIN FETCH u.department")
    List<User> findAllWithDepartment();
    
    // 解决方案2:EntityGraph
    @EntityGraph(attributePaths = {"department"})
    List<User> findAll();
    
    // 解决方案3:批量抓取
    @BatchSize(size = 100)
    @OneToMany(mappedBy = "department")
    private List<User> users;
}

3.3 继承映射

/**
 * JPA 继承映射策略
 */

/**
 * 1. SINGLE_TABLE - 单表继承(默认)
 * 所有子类存储在同一张表,使用区分列
 */
@Entity
@Table(name = "payment")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "payment_type", 
                     discriminatorType = DiscriminatorType.STRING)
@Data
public abstract class Payment {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private BigDecimal amount;
    
    private LocalDateTime payTime;
}

@Entity
@DiscriminatorValue("ALIPAY")
@Data
@EqualsAndHashCode(callSuper = true)
public class AlipayPayment extends Payment {
    
    private String alipayAccount;
}

@Entity
@DiscriminatorValue("WECHAT")
@Data
@EqualsAndHashCode(callSuper = true)
public class WechatPayment extends Payment {
    
    private String wechatOpenId;
}

/**
 * 2. JOINED - 联合表继承
 * 父类和子类各一张表,子类表只存储特有字段
 */
@Entity
@Table(name = "vehicle")
@Inheritance(strategy = InheritanceType.JOINED)
@Data
public abstract class Vehicle {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String brand;
    
    private Double price;
}

@Entity
@Table(name = "car")
@PrimaryKeyJoinColumn(name = "vehicle_id")
@Data
@EqualsAndHashCode(callSuper = true)
public class Car extends Vehicle {
    
    private Integer seatCount;
}

@Entity
@Table(name = "truck")
@PrimaryKeyJoinColumn(name = "vehicle_id")
@Data
@EqualsAndHashCode(callSuper = true)
public class Truck extends Vehicle {
    
    private Double loadCapacity;
}

/**
 * 3. TABLE_PER_CLASS - 每个类一张表
 * 每个具体类一张表,包含所有字段(包括父类字段)
 */
@Entity
@Table(name = "document")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Data
public abstract class Document {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)  // 不能使用 IDENTITY
    private Long id;
    
    private String title;
    
    private String content;
}

@Entity
@Table(name = "pdf_document")
@Data
@EqualsAndHashCode(callSuper = true)
public class PdfDocument extends Document {
    
    private Integer pageCount;
}

@Entity
@Table(name = "word_document")
@Data
@EqualsAndHashCode(callSuper = true)
public class WordDocument extends Document {
    
    private String author;
}

/**
 * 4. MappedSuperclass - 映射父类(非实体)
 * 父类不映射为表,只用于属性继承
 */
@MappedSuperclass
@Data
public abstract class BaseEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "create_time", updatable = false)
    private LocalDateTime createTime;
    
    @Column(name = "update_time")
    private LocalDateTime updateTime;
    
    @Version
    private Integer version;
    
    @PrePersist
    protected void prePersist() {
        this.createTime = LocalDateTime.now();
        this.updateTime = LocalDateTime.now();
    }
    
    @PreUpdate
    protected void preUpdate() {
        this.updateTime = LocalDateTime.now();
    }
}

@Entity
@Table(name = "user")
@Data
@EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {
    
    private String username;
    
    private String email;
}

@Entity
@Table(name = "product")
@Data
@EqualsAndHashCode(callSuper = true)
public class Product extends BaseEntity {
    
    private String name;
    
    private BigDecimal price;
}

四、Repository 接口

4.1 Repository 层次结构

/**
 * Spring Data JPA Repository 层次结构
 * 
 * Repository (标记接口)
 *     └── CrudRepository (CRUD 操作)
 *             └── PagingAndSortingRepository (分页排序)
 *                     └── JpaRepository (JPA 扩展)
 */

/**
 * Repository 接口定义
 */
// 标记接口
public interface Repository<T, ID> { }

// CRUD 操作
public interface CrudRepository<T, ID> extends Repository<T, ID> {
    <S extends T> S save(S entity);
    <S extends T> Iterable<S> saveAll(Iterable<S> entities);
    Optional<T> findById(ID id);
    boolean existsById(ID id);
    Iterable<T> findAll();
    Iterable<T> findAllById(Iterable<ID> ids);
    long count();
    void deleteById(ID id);
    void delete(T entity);
    void deleteAllById(Iterable<? extends ID> ids);
    void deleteAll(Iterable<? extends T> entities);
    void deleteAll();
}

// 分页排序
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
    Iterable<T> findAll(Sort sort);
    Page<T> findAll(Pageable pageable);
}

// JPA 扩展
public interface JpaRepository<T, ID> extends 
        PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    
    // 批量操作
    <S extends T> List<S> saveAllAndFlush(Iterable<S> entities);
    void deleteAllInBatch();
    void deleteAllByIdInBatch(Iterable<ID> ids);
    
    // 获取引用(延迟加载)
    T getReferenceById(ID id);
    
    // 刷新
    void flush();
    <S extends T> S saveAndFlush(S entity);
    
    // 其他
    List<T> findAll();
    List<T> findAll(Sort sort);
    List<T> findAllById(Iterable<ID> ids);
}

/**
 * 自定义 Repository
 */
// 自定义接口
public interface CustomUserRepository {
    List<User> findActiveUsers();
    void updateUserStatusBatch(List<Long> userIds, UserStatus status);
}

// 自定义实现
public class CustomUserRepositoryImpl implements CustomUserRepository {
    
    @PersistenceContext
    private EntityManager entityManager;
    
    @Override
    public List<User> findActiveUsers() {
        return entityManager.createQuery(
            "SELECT u FROM User u WHERE u.status = 'ACTIVE'", User.class)
            .getResultList();
    }
    
    @Override
    @Transactional
    public void updateUserStatusBatch(List<Long> userIds, UserStatus status) {
        entityManager.createQuery(
            "UPDATE User u SET u.status = :status WHERE u.id IN :ids")
            .setParameter("status", status)
            .setParameter("ids", userIds)
            .executeUpdate();
    }
}

// 继承自定义接口
public interface UserRepository extends JpaRepository<User, Long>, CustomUserRepository {
    // 现在可以使用 CustomUserRepository 中定义的方法
}

4.2 方法命名查询

/**
 * 方法命名查询规则
 * 
 * 格式:findBy + 属性名 + 关键字 + [And/Or] + 属性名 + 关键字
 */
public interface UserRepository extends JpaRepository<User, Long> {
    
    // ========== 比较关键字 ==========
    
    // 等于
    User findByUsername(String username);
    User findByUsernameEquals(String username);
    // WHERE username = ?
    
    // 不等于
    List<User> findByStatusNot(UserStatus status);
    // WHERE status <> ?
    
    // Like
    List<User> findByUsernameLike(String username);
    // WHERE username LIKE ?
    
    List<User> findByUsernameContaining(String username);
    // WHERE username LIKE %?%
    
    List<User> findByUsernameStartingWith(String prefix);
    // WHERE username LIKE ?%
    
    List<User> findByUsernameEndingWith(String suffix);
    // WHERE username LIKE %?
    
    // Between
    List<User> findByAgeBetween(Integer min, Integer max);
    // WHERE age BETWEEN ? AND ?
    
    List<User> findByCreateTimeBetween(LocalDateTime start, LocalDateTime end);
    // WHERE create_time BETWEEN ? AND ?
    
    // LessThan / LessThanEqual
    List<User> findByAgeLessThan(Integer age);
    // WHERE age < ?
    
    List<User> findByAgeLessThanEqual(Integer age);
    // WHERE age <= ?
    
    // GreaterThan / GreaterThanEqual
    List<User> findByAgeGreaterThan(Integer age);
    // WHERE age > ?
    
    List<User> findByAgeGreaterThanEqual(Integer age);
    // WHERE age >= ?
    
    // In
    List<User> findByStatusIn(List<UserStatus> statuses);
    // WHERE status IN (?)
    
    List<User> findByIdIn(List<Long> ids);
    // WHERE id IN (?)
    
    // NotIn
    List<User> findByStatusNotIn(List<UserStatus> statuses);
    // WHERE status NOT IN (?)
    
    // Null / NotNull
    List<User> findByEmailIsNull();
    // WHERE email IS NULL
    
    List<User> findByEmailNotNull();
    // WHERE email IS NOT NULL
    
    // True / False(适用于 boolean)
    List<User> findByActiveTrue();
    // WHERE active = true
    
    List<User> findByActiveFalse();
    // WHERE active = false
    
    // ========== 排序 ==========
    
    List<User> findByStatusOrderByCreateTimeDesc(UserStatus status);
    // WHERE status = ? ORDER BY create_time DESC
    
    List<User> findByStatusOrderByCreateTimeDescUsernameAsc(UserStatus status);
    // WHERE status = ? ORDER BY create_time DESC, username ASC
    
    // ========== 限制结果 ==========
    
    User findFirstByUsername(String username);
    // LIMIT 1
    
    User findTopByUsername(String username);
    // LIMIT 1
    
    List<User> findFirst10ByStatus(UserStatus status);
    // LIMIT 10
    
    List<User> findTop5ByStatusOrderByCreateTimeDesc(UserStatus status);
    // LIMIT 5
    
    // ========== Distinct ==========
    
    List<User> findDistinctByUsername(String username);
    // SELECT DISTINCT ...
    
    // ========== 逻辑操作符 ==========
    
    List<User> findByUsernameAndStatus(String username, UserStatus status);
    // WHERE username = ? AND status = ?
    
    List<User> findByUsernameOrEmail(String username, String email);
    // WHERE username = ? OR email = ?
    
    // ========== 嵌套属性 ==========
    
    List<User> findByDepartmentName(String deptName);
    // WHERE department.name = ?
    
    List<User> findByDepartmentId(Long deptId);
    // WHERE department.id = ?
    
    // ========== 分页和排序参数 ==========
    
    List<User> findByStatus(UserStatus status, Sort sort);
    
    Page<User> findByStatus(UserStatus status, Pageable pageable);
    
    Slice<User> findByUsernameLike(String username, Pageable pageable);
    // Slice 只查询当前页,不查 count
}

/**
 * 使用示例
 */
@SpringBootTest
public class MethodQueryTest {
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    public void testMethodQuery() {
        // 简单查询
        User user = userRepository.findByUsername("zhangsan");
        
        // Like 查询
        List<User> users = userRepository.findByUsernameContaining("张");
        
        // Between 查询
        List<User> ageUsers = userRepository.findByAgeBetween(20, 30);
        
        // In 查询
        List<User> statusUsers = userRepository.findByStatusIn(
            Arrays.asList(UserStatus.ACTIVE, UserStatus.INACTIVE));
        
        // 分页查询
        Page<User> page = userRepository.findByStatus(
            UserStatus.ACTIVE, 
            PageRequest.of(0, 10, Sort.by("createTime").descending())
        );
        
        // 排序查询
        List<User> sorted = userRepository.findByStatus(
            UserStatus.ACTIVE,
            Sort.by(Sort.Direction.DESC, "createTime")
                .and(Sort.by(Sort.Direction.ASC, "username"))
        );
    }
}

4.3 @Query 查询

/**
 * @Query 注解查询
 */
public interface UserRepository extends JpaRepository<User, Long> {
    
    // ========== JPQL 查询 ==========
    
    /**
     * 基本 JPQL
     */
    @Query("SELECT u FROM User u WHERE u.username = :username")
    User findByUsernameJPQL(@Param("username") String username);
    
    /**
     * 多条件查询
     */
    @Query("SELECT u FROM User u WHERE u.status = :status AND u.age >= :minAge")
    List<User> findActiveAdults(@Param("status") UserStatus status, 
                                 @Param("minAge") Integer minAge);
    
    /**
     * Like 查询
     */
    @Query("SELECT u FROM User u WHERE u.username LIKE %:keyword%")
    List<User> searchByUsername(@Param("keyword") String keyword);
    
    /**
     * 排序
     */
    @Query("SELECT u FROM User u WHERE u.status = :status ORDER BY u.createTime DESC")
    List<User> findByStatusSorted(@Param("status") UserStatus status);
    
    /**
     * 分页
     */
    @Query("SELECT u FROM User u WHERE u.status = :status")
    Page<User> findByStatusPaged(@Param("status") UserStatus status, 
                                  Pageable pageable);
    
    /**
     * 关联查询
     */
    @Query("SELECT u FROM User u JOIN FETCH u.department WHERE u.id = :id")
    User findByIdWithDepartment(@Param("id") Long id);
    
    /**
     * 统计
     */
    @Query("SELECT COUNT(u) FROM User u WHERE u.status = :status")
    long countByStatus(@Param("status") UserStatus status);
    
    /**
     * 聚合
     */
    @Query("SELECT AVG(u.age) FROM User u WHERE u.status = :status")
    Double avgAgeByStatus(@Param("status") UserStatus status);
    
    /**
     * 分组
     */
    @Query("SELECT u.status, COUNT(u) FROM User u GROUP BY u.status")
    List<Object[]> countGroupByStatus();
    
    /**
     * DTO 投影
     */
    @Query("SELECT new com.example.dto.UserDTO(u.id, u.username, u.email) " +
           "FROM User u WHERE u.status = :status")
    List<UserDTO> findUserDTOs(@Param("status") UserStatus status);
    
    // ========== 原生 SQL 查询 ==========
    
    /**
     * 原生 SQL
     */
    @Query(value = "SELECT * FROM user WHERE username = :username", 
           nativeQuery = true)
    User findByUsernameNative(@Param("username") String username);
    
    /**
     * 原生 SQL 分页
     */
    @Query(value = "SELECT * FROM user WHERE status = :status",
           countQuery = "SELECT COUNT(*) FROM user WHERE status = :status",
           nativeQuery = true)
    Page<User> findByStatusNative(@Param("status") String status, 
                                   Pageable pageable);
    
    /**
     * 原生 SQL 更新
     */
    @Modifying
    @Query(value = "UPDATE user SET status = :status WHERE id = :id", 
           nativeQuery = true)
    int updateStatusNative(@Param("id") Long id, @Param("status") String status);
    
    // ========== 更新查询 ==========
    
    /**
     * 更新
     */
    @Modifying
    @Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
    int updateStatus(@Param("id") Long id, @Param("status") UserStatus status);
    
    /**
     * 批量更新
     */
    @Modifying
    @Query("UPDATE User u SET u.status = :status WHERE u.id IN :ids")
    int batchUpdateStatus(@Param("ids") List<Long> ids, 
                          @Param("status") UserStatus status);
    
    /**
     * 删除
     */
    @Modifying
    @Query("DELETE FROM User u WHERE u.status = :status")
    int deleteByStatus(@Param("status") UserStatus status);
    
    // ========== SpEL 表达式 ==========
    
    /**
     * 使用实体名称
     */
    @Query("SELECT u FROM #{#entityName} u WHERE u.username = :username")
    User findByUsernameSpEL(@Param("username") String username);
    
    /**
     * 使用 SpEL 动态条件
     */
    @Query("SELECT u FROM User u WHERE " +
           "(:username IS NULL OR u.username = :username) AND " +
           "(:status IS NULL OR u.status = :status)")
    List<User> findByConditions(@Param("username") String username,
                                 @Param("status") UserStatus status);
}

/**
 * 更新查询使用
 */
@SpringBootTest
public class QueryTest {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 更新操作需要 @Transactional
     */
    @Test
    @Transactional
    public void testUpdate() {
        int rows = userRepository.updateStatus(1L, UserStatus.INACTIVE);
        System.out.println("更新行数: " + rows);
    }
    
    /**
     * 批量更新
     */
    @Test
    @Transactional
    public void testBatchUpdate() {
        int rows = userRepository.batchUpdateStatus(
            Arrays.asList(1L, 2L, 3L), 
            UserStatus.DELETED
        );
        System.out.println("更新行数: " + rows);
    }
    
    /**
     * 清除持久化上下文(更新后刷新)
     */
    @Test
    @Transactional
    @Modifying(clearAutomatically = true, flushAutomatically = true)
    public void testUpdateWithClear() {
        userRepository.updateStatus(1L, UserStatus.INACTIVE);
    }
}

4.4 Specification 动态查询

/**
 * Specification 动态查询
 */
public interface UserSpecificationRepository 
        extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
    // JpaSpecificationExecutor 提供以下方法:
    // Optional<T> findOne(@Nullable Specification<T> spec);
    // List<T> findAll(@Nullable Specification<T> spec);
    // Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
    // List<T> findAll(@Nullable Specification<T> spec, Sort sort);
    // long count(@Nullable Specification<T> spec);
    // boolean exists(@Nullable Specification<T> spec);
}

/**
 * Specification 工具类
 */
public class UserSpecifications {
    
    /**
     * 用户名模糊查询
     */
    public static Specification<User> usernameLike(String username) {
        return (root, query, cb) -> {
            if (StringUtils.hasText(username)) {
                return cb.like(root.get("username"), "%" + username + "%");
            }
            return null;
        };
    }
    
    /**
     * 状态等于
     */
    public static Specification<User> statusEq(UserStatus status) {
        return (root, query, cb) -> {
            if (status != null) {
                return cb.equal(root.get("status"), status);
            }
            return null;
        };
    }
    
    /**
     * 年龄范围
     */
    public static Specification<User> ageBetween(Integer minAge, Integer maxAge) {
        return (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (minAge != null) {
                predicates.add(cb.ge(root.get("age"), minAge));
            }
            if (maxAge != null) {
                predicates.add(cb.le(root.get("age"), maxAge));
            }
            return cb.and(predicates.toArray(new Predicate[0]));
        };
    }
    
    /**
     * 部门 ID
     */
    public static Specification<User> departmentIdEq(Long deptId) {
        return (root, query, cb) -> {
            if (deptId != null) {
                return cb.equal(root.get("department").get("id"), deptId);
            }
            return null;
        };
    }
    
    /**
     * 创建时间范围
     */
    public static Specification<User> createTimeBetween(
            LocalDateTime start, LocalDateTime end) {
        return (root, query, cb) -> {
            if (start != null && end != null) {
                return cb.between(root.get("createTime"), start, end);
            }
            return null;
        };
    }
    
    /**
     * 动态组合条件
     */
    public static Specification<User> buildQuery(UserQuery query) {
        return Specification
            .where(usernameLike(query.getUsername()))
            .and(statusEq(query.getStatus()))
            .and(ageBetween(query.getMinAge(), query.getMaxAge()))
            .and(departmentIdEq(query.getDeptId()))
            .and(createTimeBetween(query.getStartTime(), query.getEndTime()));
    }
}

/**
 * 查询条件对象
 */
@Data
public class UserQuery {
    private String username;
    private UserStatus status;
    private Integer minAge;
    private Integer maxAge;
    private Long deptId;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
}

/**
 * 使用示例
 */
@SpringBootTest
public class SpecificationTest {
    
    @Autowired
    private UserSpecificationRepository userRepository;
    
    /**
     * 基本使用
     */
    @Test
    public void testSpecification() {
        Specification<User> spec = (root, query, cb) -> {
            return cb.and(
                cb.equal(root.get("status"), UserStatus.ACTIVE),
                cb.like(root.get("username"), "%张%")
            );
        };
        
        List<User> users = userRepository.findAll(spec);
    }
    
    /**
     * 使用工具类
     */
    @Test
    public void testSpecificationUtils() {
        UserQuery query = new UserQuery();
        query.setUsername("张");
        query.setStatus(UserStatus.ACTIVE);
        query.setMinAge(20);
        query.setMaxAge(30);
        
        Specification<User> spec = UserSpecifications.buildQuery(query);
        
        List<User> users = userRepository.findAll(spec);
    }
    
    /**
     * 分页查询
     */
    @Test
    public void testSpecificationPage() {
        UserQuery query = new UserQuery();
        query.setStatus(UserStatus.ACTIVE);
        
        Specification<User> spec = UserSpecifications.buildQuery(query);
        
        Page<User> page = userRepository.findAll(spec, 
            PageRequest.of(0, 10, Sort.by("createTime").descending()));
        
        System.out.println("总数: " + page.getTotalElements());
        System.out.println("数据: " + page.getContent());
    }
    
    /**
     * 复杂查询示例
     */
    @Test
    public void testComplexQuery() {
        Specification<User> spec = (root, query, cb) -> {
            // 多表关联
            Join<User, Department> deptJoin = root.join("department", JoinType.LEFT);
            
            // 多条件
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("status"), UserStatus.ACTIVE));
            predicates.add(cb.isNotNull(root.get("email")));
            predicates.add(cb.like(deptJoin.get("name"), "%技术%"));
            
            // OR 条件
            Predicate orPredicate = cb.or(
                cb.like(root.get("username"), "%张%"),
                cb.like(root.get("email"), "%test%")
            );
            predicates.add(orPredicate);
            
            return cb.and(predicates.toArray(new Predicate[0]));
        };
        
        List<User> users = userRepository.findAll(spec);
    }
}

五、实体监听器与审计

5.1 实体生命周期回调

/**
 * JPA 实体生命周期回调
 */
@Entity
@Table(name = "user")
@Data
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    private LocalDateTime createTime;
    
    private LocalDateTime updateTime;
    
    private String createBy;
    
    private String updateBy;
    
    // ========== 生命周期回调方法 ==========
    
    /**
     * @PrePersist - 持久化前
     */
    @PrePersist
    public void prePersist() {
        this.createTime = LocalDateTime.now();
        this.updateTime = LocalDateTime.now();
        this.createBy = getCurrentUser();
        this.updateBy = getCurrentUser();
    }
    
    /**
     * @PostPersist - 持久化后
     */
    @PostPersist
    public void postPersist() {
        System.out.println("用户已创建: " + this.id);
    }
    
    /**
     * @PreUpdate - 更新前
     */
    @PreUpdate
    public void preUpdate() {
        this.updateTime = LocalDateTime.now();
        this.updateBy = getCurrentUser();
    }
    
    /**
     * @PostUpdate - 更新后
     */
    @PostUpdate
    public void postUpdate() {
        System.out.println("用户已更新: " + this.id);
    }
    
    /**
     * @PreRemove - 删除前
     */
    @PreRemove
    public void preRemove() {
        System.out.println("即将删除用户: " + this.id);
    }
    
    /**
     * @PostRemove - 删除后
     */
    @PostRemove
    public void postRemove() {
        System.out.println("用户已删除: " + this.id);
    }
    
    /**
     * @PostLoad - 加载后
     */
    @PostLoad
    public void postLoad() {
        // 可以在此进行数据初始化
    }
    
    private String getCurrentUser() {
        return SecurityContextHolder.getContext()
            .getAuthentication().getName();
    }
}

/**
 * 外部实体监听器
 */
public class UserEntityListener {
    
    @PrePersist
    public void onPrePersist(User user) {
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
    }
    
    @PreUpdate
    public void onPreUpdate(User user) {
        user.setUpdateTime(LocalDateTime.now());
    }
}

@Entity
@Table(name = "user")
@EntityListeners(UserEntityListener.class)  // 注册监听器
@Data
public class UserWithListener {
    // ...
}

/**
 * 全局实体监听器配置
 */
// orm.xml
/*
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
                 version="2.2">
    <persistence-unit-metadata>
        <persistence-unit-defaults>
            <entity-listeners>
                <entity-listener class="com.example.listener.GlobalEntityListener"/>
            </entity-listeners>
        </persistence-unit-defaults>
    </persistence-unit-metadata>
</entity-mappings>
*/

5.2 Spring Data JPA 审计

/**
 * Spring Data JPA 审计功能
 */
// 1. 启用审计
@Configuration
@EnableJpaAuditing
public class JpaConfig {
    
    @Bean
    public AuditorAware<String> auditorProvider() {
        return () -> {
            // 获取当前用户
            Authentication auth = SecurityContextHolder.getContext()
                .getAuthentication();
            if (auth != null && auth.isAuthenticated()) {
                return Optional.of(auth.getName());
            }
            return Optional.of("system");
        };
    }
}

// 2. 基础实体类
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Data
public abstract class AuditableEntity {
    
    @CreatedDate
    @Column(name = "create_time", updatable = false)
    private LocalDateTime createTime;
    
    @LastModifiedDate
    @Column(name = "update_time")
    private LocalDateTime updateTime;
    
    @CreatedBy
    @Column(name = "create_by", length = 50, updatable = false)
    private String createBy;
    
    @LastModifiedBy
    @Column(name = "update_by", length = 50)
    private String updateBy;
}

// 3. 实体类继承
@Entity
@Table(name = "user")
@Data
@EqualsAndHashCode(callSuper = true)
public class User extends AuditableEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    private String email;
}

@Entity
@Table(name = "product")
@Data
@EqualsAndHashCode(callSuper = true)
public class Product extends AuditableEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    private BigDecimal price;
}

六、性能优化

6.1 延迟加载与 N+1 问题

/**
 * 延迟加载与 N+1 问题
 */
@Entity
@Table(name = "user")
@Data
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    // 延迟加载部门
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "dept_id")
    private Department department;
    
    // 延迟加载角色
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "user_role",
               joinColumns = @JoinColumn(name = "user_id"),
               inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles = new HashSet<>();
}

/**
 * 解决 N+1 问题
 */
public interface UserRepository extends JpaRepository<User, Long> {
    
    // 方式1:JPQL JOIN FETCH
    @Query("SELECT u FROM User u JOIN FETCH u.department WHERE u.id = :id")
    User findByIdWithDepartment(@Param("id") Long id);
    
    @Query("SELECT u FROM User u LEFT JOIN FETCH u.department")
    List<User> findAllWithDepartment();
    
    @Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.roles")
    List<User> findAllWithRoles();
    
    // 方式2:EntityGraph
    @EntityGraph(attributePaths = {"department"})
    @Query("SELECT u FROM User u")
    List<User> findAllWithDeptGraph();
    
    @EntityGraph(attributePaths = {"department", "roles"})
    List<User> findAll();
    
    // 方式3:NamedEntityGraph(在实体上定义)
    // @NamedEntityGraphs({
    //     @NamedEntityGraph(name = "User.withDepartment", 
    //                       attributeNodes = @NamedAttributeNode("department"))
    // })
    @EntityGraph(value = "User.withDepartment", type = EntityGraphType.FETCH)
    List<User> findAll();
}

/**
 * 批量抓取配置
 */
@Entity
@Table(name = "department")
@Data
public class Department {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    // 配置批量抓取
    @BatchSize(size = 50)
    @OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
    private List<User> users = new ArrayList<>();
}

/**
 * application.yml 配置
 */
/*
spring:
  jpa:
    properties:
      hibernate:
        default_batch_fetch_size: 100
        batch_fetch_style: PADDED
*/

6.2 批量操作优化

/**
 * 批量插入优化
 */
@Configuration
public class JpaBatchConfig {
    
    // 配置批量插入
    // spring.jpa.properties.hibernate.jdbc.batch_size=50
    // spring.jpa.properties.hibernate.order_inserts=true
    // spring.jpa.properties.hibernate.order_updates=true
}

@Service
@Transactional
public class UserBatchService {
    
    @PersistenceContext
    private EntityManager entityManager;
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 批量插入 - saveAll
     */
    public void batchInsertWithSaveAll(List<User> users) {
        userRepository.saveAll(users);
    }
    
    /**
     * 批量插入 - 分批处理
     */
    public void batchInsertWithBatch(List<User> users, int batchSize) {
        List<User> batch = new ArrayList<>(batchSize);
        
        for (int i = 0; i < users.size(); i++) {
            batch.add(users.get(i));
            
            if (batch.size() >= batchSize) {
                userRepository.saveAll(batch);
                entityManager.flush();
                entityManager.clear();  // 清除持久化上下文
                batch.clear();
            }
        }
        
        if (!batch.isEmpty()) {
            userRepository.saveAll(batch);
        }
    }
    
    /**
     * 批量更新
     */
    @Modifying
    @Query("UPDATE User u SET u.status = :status WHERE u.id IN :ids")
    int batchUpdateStatus(@Param("ids") List<Long> ids, 
                          @Param("status") UserStatus status);
}

/**
 * 原生 SQL 批量操作
 */
public interface UserRepository extends JpaRepository<User, Long> {
    
    /**
     * 批量插入(原生 SQL)
     */
    @Modifying
    @Query(value = "INSERT INTO user(username, email, age) VALUES " +
           "(:username1, :email1, :age1), " +
           "(:username2, :email2, :age2)", 
           nativeQuery = true)
    void batchInsertNative(@Param("username1") String username1,
                           @Param("email1") String email1,
                           @Param("age1") Integer age1,
                           @Param("username2") String username2,
                           @Param("email2") String email2,
                           @Param("age2") Integer age2);
}

6.3 缓存配置

/**
 * JPA 二级缓存配置
 */
// 1. 引入依赖
/*
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
</dependency>
*/

// 2. 配置文件
/*
spring:
  jpa:
    properties:
      hibernate:
        cache:
          use_second_level_cache: true
          use_query_cache: true
          region:
            factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory
*/

// 3. 实体配置
@Entity
@Table(name = "user")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "userCache")
@Data
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    // 缓存关联对象
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "dept_id")
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Department department;
}

// 4. 查询缓存
public interface UserRepository extends JpaRepository<User, Long> {
    
    @QueryHints(@QueryHint(name = org.hibernate.annotations.QueryHints.CACHEABLE, 
                           value = "true"))
    List<User> findByStatus(UserStatus status);
}

// 5. ehcache.xml 配置
/*
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <defaultCache
        maxElementsInMemory="1000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="false"
    />
    
    <cache name="userCache"
           maxElementsInMemory="500"
           eternal="false"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
    />
</ehcache>
*/

七、总结与最佳实践

7.1 JPA vs MyBatis 对比

┌─────────────────────────────────────────────────────────────────────────┐
│                     JPA vs MyBatis 对比                                  │
├──────────────────────┬──────────────────────┬───────────────────────────┤
│        特性          │         JPA          │         MyBatis           │
├──────────────────────┼──────────────────────┼───────────────────────────┤
│ 学习曲线             │ 较陡峭               │ 较平缓                    │
│ SQL 控制             │ 弱(自动生成)        │ 强(完全控制)             │
│ 跨数据库             │ 强(方言支持)        │ 弱(需适配)              │
│ 复杂查询             │ 较难(Specification)│ 简单(XML/注解)          │
│ 性能优化             │ 较难                 │ 简单                      │
│ 开发效率             │ 高(少写代码)        │ 中等                      │
│ 动态 SQL             │ 弱                   │ 强                        │
│ 缓存                 │ 内置                 │ 需配置                    │
│ 适用场景             │ 标准 CRUD、简单查询   │ 复杂查询、性能敏感        │
└──────────────────────┴──────────────────────┴───────────────────────────┘

7.2 最佳实践

  1. 实体设计

    • 使用 @MappedSuperclass 提取公共字段
    • 谨慎使用关联映射,避免过度关联
    • 优先使用延迟加载
  2. Repository 设计

    • 方法命名遵循规范
    • 复杂查询使用 @Query 或 Specification
    • 分离读操作和写操作
  3. 性能优化

    • 解决 N+1 问题(JOIN FETCH、EntityGraph)
    • 合理使用批量操作
    • 考虑缓存策略
  4. 事务管理

    • 合理设置事务边界
    • 避免长事务
    • 只读操作使用 @Transactional(readOnly = true)

参考资料:


六、思考与练习

思考题

  1. 基础题:JPA 的 Repository 接口继承体系是怎样的?JpaRepository 相比 CrudRepository 提供了哪些额外功能?

  2. 进阶题:什么是 N+1 问题?在 JPA 中如何通过 JOIN FETCH 和 EntityGraph 解决这个问题?

  3. 实战题:在一个内容管理系统中,文章(Article)和标签(Tag)是多对多关系,如何设计实体映射?如何避免循环引用导致的 JSON 序列化问题?

编程练习

练习:实现一个在线商城的商品管理模块,要求:

  1. 设计商品(Product)、分类(Category)、品牌(Brand)三个实体及其关联关系
  2. 使用方法命名查询实现:按分类查询、按品牌查询、价格区间查询
  3. 使用 @Query 注解实现:查询热门商品(销量前 10)、查询新品(创建时间最近 7 天)
  4. 使用 Specification 实现多条件动态查询(分类、品牌、价格区间、关键词)
  5. 实现分页查询,并使用 @EntityGraph 解决关联查询的 N+1 问题

章节关联

  • 前置章节:MyBatis-Plus 详解
  • 后续章节:多数据源配置详解
  • 扩展阅读
    • Hibernate 性能优化实战
    • JPA 与 MyBatis 混合使用指南
    • 领域驱动设计(DDD)中的 Repository 模式

📝 下一章预告

在实际企业项目中,往往需要同时访问多个数据库,如读写分离、分库分表、异构数据库等。下一章将深入讲解多数据源的配置方案,包括静态配置、动态切换、读写分离以及分布式事务处理。


本章完