轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

  • Spring

    • 基础

    • 框架

    • Spring Boot

    • 集成

      • Thymeleaf 与 Spring 框架的集成教程
      • Spring Data JPA 完全指南
        • 一、前言
        • 二、JPA基础概念
          • 1、什么是JPA
          • 2、JPA vs Hibernate vs MyBatis
          • 3、Spring Data JPA是什么
        • 三、快速上手
          • 1、环境搭建
          • 2、第一个实体类
          • 3、创建Repository
          • 4、使用示例
        • 四、实体映射详解
          • 1、基本注解
          • 1.1、@Entity和@Table
          • 1.2、@Id和主键生成策略
          • 1.3、@Column详解
          • 2、关系映射
          • 2.1、一对一(@OneToOne)
          • 2.2、一对多(@OneToMany)和多对一(@ManyToOne)
          • 2.3、多对多(@ManyToMany)
          • 3、级联操作(Cascade)
          • 4、加载策略(Fetch)
        • 五、Repository详解
          • 1、Repository继承体系
          • 1.1、CrudRepository
          • 1.2、PagingAndSortingRepository
          • 1.3、JpaRepository(推荐使用)
          • 2、方法命名查询
          • 2.1、基本规则
          • 2.2、常用关键字
          • 2.3、复杂示例
          • 3、@Query自定义查询
          • 3.1、JPQL查询
          • 3.2、原生SQL查询
          • 4、分页和排序
          • 4.1、排序(Sort)
          • 4.2、分页(Pageable)
        • 六、Specifications动态查询
          • 1、启用Specifications
          • 2、基本使用
          • 3、封装Specification工具类
        • 七、事务管理
          • 1、声明式事务
          • 2、事务属性
          • 3、事务传播行为
          • 4、只读事务
        • 八、审计功能
          • 1、启用审计
          • 2、使用审计注解
          • 3、提供审计人信息
          • 4、抽取基类
        • 九、性能优化
          • 1、N+1查询问题
          • 2、批量操作优化
          • 3、投影查询
          • 4、缓存
          • 5、懒加载优化
        • 十、最佳实践
          • 1、实体设计原则
          • 2、Repository设计原则
          • 3、Service设计原则
          • 4、性能优化建议
          • 5、常见错误
        • 十一、总结
  • 其他语言

  • 工具

  • 后端
  • Spring
  • 集成
轩辕李
2023-12-06
目录

Spring Data JPA 完全指南

# 一、前言

在Java开发中,数据库操作是绕不开的话题。你是否曾为写大量的JDBC模板代码而烦恼?是否在处理复杂的对象关系映射时感到困惑?JPA(Java Persistence API)就是为了解决这些问题而诞生的。

JPA是Java官方提供的ORM(对象关系映射)规范,它让你可以用面向对象的方式来操作数据库,而不用写繁琐的SQL语句。Spring Data JPA则是Spring对JPA的再次封装,让数据访问变得更加简单。

本文将带你从零开始,全面掌握JPA和Spring Data JPA的使用。无论你是初学者还是有一定经验的开发者,相信都能从中获益。

# 二、JPA基础概念

# 1、什么是JPA

JPA(Java Persistence API)是Java EE的一个标准规范,用于对象关系映射(ORM)。它定义了一套标准的API,让Java对象可以方便地映射到数据库表。

简单来说,JPA让你可以:

  • 用Java类表示数据库表:不用手动创建表结构
  • 用对象操作代替SQL:增删改查都是对象操作
  • 自动处理关系映射:一对一、一对多、多对多关系自动维护
  • 跨数据库移植:换数据库不用改代码

# 2、JPA vs Hibernate vs MyBatis

很多人会混淆这三者的关系,我们来理清一下:

  • JPA:是一个规范、标准,类似于JDBC
  • Hibernate:是JPA规范的一个实现,也是最流行的实现
  • MyBatis:不是JPA实现,是另一种持久层框架,更偏向SQL

三者对比:

特性 JPA/Hibernate MyBatis
编程方式 面向对象 面向SQL
学习曲线 较陡 较平缓
SQL控制 自动生成 完全控制
复杂查询 JPQL/Criteria API XML/注解映射
适用场景 标准CRUD、领域模型复杂 复杂SQL、动态查询

# 3、Spring Data JPA是什么

Spring Data JPA是Spring对JPA的进一步封装,它提供了:

  • Repository接口:只需定义接口,Spring自动实现
  • 方法名查询:根据方法名自动生成查询
  • 自定义查询:支持@Query注解
  • 分页排序:内置分页和排序支持
  • 审计功能:自动记录创建时间、修改时间等

可以说,Spring Data JPA = JPA + Spring的便利性,让数据访问层的开发效率大大提升。

# 三、快速上手

# 1、环境搭建

首先在pom.xml中添加依赖:

<dependencies>
    <!-- Spring Boot Starter Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- H2数据库(用于测试) -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

然后在application.yml中配置数据源:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    
  jpa:
    # 是否显示SQL语句
    show-sql: true
    # Hibernate配置
    hibernate:
      # ddl-auto: create-drop  # 测试时使用,会删除并重建表
      # ddl-auto: update       # 开发时使用,会更新表结构
      ddl-auto: validate       # 生产环境使用,只验证不修改
    # 数据库方言
    database-platform: org.hibernate.dialect.MySQLDialect
    # 格式化SQL输出
    properties:
      hibernate:
        format_sql: true

ddl-auto配置说明:

  • create:每次启动都删除并创建表(危险)
  • create-drop:启动时创建,关闭时删除(测试用)
  • update:自动更新表结构,不删除数据(开发用)
  • validate:只验证表结构,不做修改(生产用)
  • none:不做任何操作

# 2、第一个实体类

创建一个用户实体:

package com.example.entity;

import jakarta.persistence.*;
import java.time.LocalDateTime;

/**
 * 用户实体类
 */
@Entity
@Table(name = "t_user")
public class User {
    
    /**
     * 主键ID
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    /**
     * 用户名(唯一)
     */
    @Column(nullable = false, unique = true, length = 50)
    private String username;
    
    /**
     * 邮箱
     */
    @Column(nullable = false, length = 100)
    private String email;
    
    /**
     * 年龄
     */
    @Column
    private Integer age;
    
    /**
     * 创建时间
     */
    @Column(name = "create_time", nullable = false, updatable = false)
    private LocalDateTime createTime;
    
    /**
     * 更新时间
     */
    @Column(name = "update_time")
    private LocalDateTime updateTime;
    
    // 在插入前自动设置创建时间
    @PrePersist
    protected void onCreate() {
        createTime = LocalDateTime.now();
        updateTime = LocalDateTime.now();
    }
    
    // 在更新前自动设置更新时间
    @PreUpdate
    protected void onUpdate() {
        updateTime = LocalDateTime.now();
    }
    
    // Getter和Setter方法
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public LocalDateTime getCreateTime() {
        return createTime;
    }

    public void setCreateTime(LocalDateTime createTime) {
        this.createTime = createTime;
    }

    public LocalDateTime getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(LocalDateTime updateTime) {
        this.updateTime = updateTime;
    }
}

注解说明:

  • @Entity:标记这是一个JPA实体类
  • @Table:指定对应的数据库表名
  • @Id:标记主键字段
  • @GeneratedValue:主键生成策略
  • @Column:指定列的属性(名称、长度、是否可空等)
  • @PrePersist:插入前执行的方法
  • @PreUpdate:更新前执行的方法

# 3、创建Repository

只需定义一个接口继承JpaRepository:

package com.example.repository;

import com.example.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

/**
 * 用户数据访问接口
 */
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    // Spring Data JPA会根据方法名自动生成查询
    
    /**
     * 根据用户名查询用户
     */
    Optional<User> findByUsername(String username);
    
    /**
     * 根据邮箱查询用户
     */
    Optional<User> findByEmail(String email);
    
    /**
     * 查询年龄大于指定值的用户
     */
    List<User> findByAgeGreaterThan(Integer age);
    
    /**
     * 查询用户名包含指定字符串的用户
     */
    List<User> findByUsernameContaining(String keyword);
}

没错,就这么简单!你不需要写任何实现代码,Spring Data JPA会自动帮你实现这些方法。

# 4、使用示例

在Service中使用Repository:

package com.example.service;

import com.example.entity.User;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

/**
 * 用户服务类
 */
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 创建用户
     */
    @Transactional
    public User createUser(String username, String email, Integer age) {
        User user = new User();
        user.setUsername(username);
        user.setEmail(email);
        user.setAge(age);
        return userRepository.save(user);
    }
    
    /**
     * 根据ID查询用户
     */
    public Optional<User> getUserById(Long id) {
        return userRepository.findById(id);
    }
    
    /**
     * 根据用户名查询用户
     */
    public Optional<User> getUserByUsername(String username) {
        return userRepository.findByUsername(username);
    }
    
    /**
     * 查询所有用户
     */
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    /**
     * 更新用户
     */
    @Transactional
    public User updateUser(Long id, String email, Integer age) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("用户不存在"));
        user.setEmail(email);
        user.setAge(age);
        return userRepository.save(user);
    }
    
    /**
     * 删除用户
     */
    @Transactional
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
    
    /**
     * 查询年龄大于指定值的用户
     */
    public List<User> getUsersOlderThan(Integer age) {
        return userRepository.findByAgeGreaterThan(age);
    }
}

是不是很简单?不需要写一行SQL,就完成了基本的增删改查功能。

# 四、实体映射详解

# 1、基本注解

# 1.1、@Entity和@Table

@Entity  // 标记为JPA实体
@Table(
    name = "t_user",              // 表名
    schema = "mydb",              // 数据库schema
    catalog = "catalog_name",     // catalog名称
    uniqueConstraints = {         // 唯一约束
        @UniqueConstraint(
            name = "uk_username_email",
            columnNames = {"username", "email"}
        )
    },
    indexes = {                   // 索引
        @Index(name = "idx_age", columnList = "age")
    }
)
public class User {
    // ...
}

# 1.2、@Id和主键生成策略

public class User {
    
    // 策略1:自增(最常用,适合MySQL)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    // 策略2:序列(适合Oracle、PostgreSQL)
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
    @SequenceGenerator(name = "user_seq", sequenceName = "user_sequence", allocationSize = 1)
    private Long id;
    
    // 策略3:UUID
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private String id;
    
    // 策略4:自定义生成器
    @Id
    @GeneratedValue(generator = "custom-generator")
    @GenericGenerator(name = "custom-generator", strategy = "com.example.CustomIdGenerator")
    private String id;
}

主键生成策略说明:

  • IDENTITY:数据库自增,适合MySQL、SQL Server
  • SEQUENCE:使用数据库序列,适合Oracle、PostgreSQL
  • TABLE:使用表来模拟序列,性能较差,不推荐
  • AUTO:自动选择策略,不推荐(不确定性高)
  • UUID:使用UUID,适合分布式系统

# 1.3、@Column详解

public class User {
    
    @Column(
        name = "user_name",        // 列名
        nullable = false,          // 是否可为空
        unique = true,             // 是否唯一
        length = 50,               // 字符串长度
        precision = 10,            // 数字总位数
        scale = 2,                 // 小数位数
        insertable = true,         // 是否可插入
        updatable = true,          // 是否可更新
        columnDefinition = "VARCHAR(50) COMMENT '用户名'"  // 自定义列定义
    )
    private String username;
    
    // 对于枚举类型
    @Enumerated(EnumType.STRING)  // 存储枚举名称(推荐)
    private UserStatus status;
    
    // 对于日期类型
    @Temporal(TemporalType.TIMESTAMP)  // JPA 2.x中使用
    private Date createTime;
    
    // JPA 3.x推荐使用Java 8时间API
    private LocalDateTime updateTime;  // 不需要@Temporal
    
    // 对于大字段
    @Lob  // Large Object
    @Column(columnDefinition = "TEXT")
    private String content;
    
    // 不需要持久化的字段
    @Transient
    private String tempData;
}

# 2、关系映射

# 2.1、一对一(@OneToOne)

示例:用户与用户详情

/**
 * 用户实体
 */
@Entity
@Table(name = "t_user")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    // 一对一关系(主控方)
    @OneToOne(
        cascade = CascadeType.ALL,    // 级联操作
        fetch = FetchType.LAZY,       // 懒加载
        orphanRemoval = true          // 删除孤儿记录
    )
    @JoinColumn(name = "profile_id", unique = true)  // 外键列
    private UserProfile profile;
    
    // getter/setter...
}

/**
 * 用户详情实体
 */
@Entity
@Table(name = "t_user_profile")
public class UserProfile {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String address;
    private String phone;
    
    // 一对一关系(被控方)
    @OneToOne(mappedBy = "profile")  // mappedBy指向主控方的属性名
    private User user;
    
    // getter/setter...
}

使用示例:

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Transactional
    public void createUserWithProfile() {
        // 创建用户详情
        UserProfile profile = new UserProfile();
        profile.setAddress("北京市朝阳区");
        profile.setPhone("13800138000");
        
        // 创建用户
        User user = new User();
        user.setUsername("zhangsan");
        user.setProfile(profile);  // 关联详情
        
        // 保存用户(级联保存详情)
        userRepository.save(user);
    }
}

# 2.2、一对多(@OneToMany)和多对一(@ManyToOne)

示例:部门与员工

/**
 * 部门实体(一方)
 */
@Entity
@Table(name = "t_department")
public class Department {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    // 一对多关系
    @OneToMany(
        mappedBy = "department",      // 指向Employee中的department属性
        cascade = CascadeType.ALL,
        orphanRemoval = true,
        fetch = FetchType.LAZY
    )
    private List<Employee> employees = new ArrayList<>();
    
    // 便捷方法:添加员工
    public void addEmployee(Employee employee) {
        employees.add(employee);
        employee.setDepartment(this);  // 维护双向关系
    }
    
    // 便捷方法:移除员工
    public void removeEmployee(Employee employee) {
        employees.remove(employee);
        employee.setDepartment(null);
    }
    
    // getter/setter...
}

/**
 * 员工实体(多方)
 */
@Entity
@Table(name = "t_employee")
public class Employee {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    // 多对一关系
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")  // 外键列
    private Department department;
    
    // getter/setter...
}

使用示例:

@Service
public class DepartmentService {
    
    @Autowired
    private DepartmentRepository departmentRepository;
    
    @Transactional
    public void createDepartmentWithEmployees() {
        // 创建部门
        Department dept = new Department();
        dept.setName("研发部");
        
        // 创建员工
        Employee emp1 = new Employee();
        emp1.setName("张三");
        
        Employee emp2 = new Employee();
        emp2.setName("李四");
        
        // 添加员工到部门
        dept.addEmployee(emp1);
        dept.addEmployee(emp2);
        
        // 保存部门(级联保存员工)
        departmentRepository.save(dept);
    }
}

# 2.3、多对多(@ManyToMany)

示例:学生与课程

/**
 * 学生实体
 */
@Entity
@Table(name = "t_student")
public class Student {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    // 多对多关系(主控方)
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(
        name = "t_student_course",           // 中间表名
        joinColumns = @JoinColumn(name = "student_id"),        // 当前实体的外键
        inverseJoinColumns = @JoinColumn(name = "course_id")   // 关联实体的外键
    )
    private Set<Course> courses = new HashSet<>();
    
    // 便捷方法:选课
    public void enrollCourse(Course course) {
        courses.add(course);
        course.getStudents().add(this);
    }
    
    // 便捷方法:退课
    public void dropCourse(Course course) {
        courses.remove(course);
        course.getStudents().remove(this);
    }
    
    // getter/setter...
}

/**
 * 课程实体
 */
@Entity
@Table(name = "t_course")
public class Course {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    // 多对多关系(被控方)
    @ManyToMany(mappedBy = "courses")
    private Set<Student> students = new HashSet<>();
    
    // getter/setter...
}

使用示例:

@Service
public class StudentService {
    
    @Autowired
    private StudentRepository studentRepository;
    
    @Autowired
    private CourseRepository courseRepository;
    
    @Transactional
    public void enrollStudentToCourses() {
        // 创建学生
        Student student = new Student();
        student.setName("王五");
        
        // 查询已存在的课程
        Course javaCourse = courseRepository.findByName("Java编程").orElseThrow();
        Course springCourse = courseRepository.findByName("Spring框架").orElseThrow();
        
        // 学生选课
        student.enrollCourse(javaCourse);
        student.enrollCourse(springCourse);
        
        // 保存学生
        studentRepository.save(student);
    }
}

# 3、级联操作(Cascade)

级联操作决定了对主实体的操作是否会传播到关联实体:

public enum CascadeType {
    PERSIST,   // 保存主实体时,也保存关联实体
    MERGE,     // 更新主实体时,也更新关联实体
    REMOVE,    // 删除主实体时,也删除关联实体
    REFRESH,   // 刷新主实体时,也刷新关联实体
    DETACH,    // 分离主实体时,也分离关联实体
    ALL        // 以上全部
}

使用建议:

  • 一对一:通常使用CascadeType.ALL
  • 一对多:父实体使用CascadeType.ALL,子实体谨慎使用REMOVE
  • 多对多:只使用PERSIST和MERGE,避免误删

# 4、加载策略(Fetch)

public enum FetchType {
    LAZY,   // 懒加载:访问时才加载(推荐)
    EAGER   // 急加载:立即加载
}

默认加载策略:

  • @OneToOne:EAGER
  • @ManyToOne:EAGER
  • @OneToMany:LAZY
  • @ManyToMany:LAZY

最佳实践:除非必要,都使用LAZY,避免N+1查询问题。

# 五、Repository详解

# 1、Repository继承体系

Spring Data JPA提供了一系列Repository接口:

Repository (标记接口)
    ├─ CrudRepository (基本增删改查)
    │   ├─ PagingAndSortingRepository (分页和排序)
    │   │   └─ JpaRepository (JPA扩展,最常用)
    │   └─ ListCrudRepository (返回List而非Iterable)
    └─ QueryByExampleExecutor (按示例查询)

# 1.1、CrudRepository

提供基本的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);                       // 根据ID查询
    boolean existsById(ID id);                         // 是否存在
    Iterable<T> findAll();                             // 查询所有
    Iterable<T> findAllById(Iterable<ID> ids);         // 批量查询
    long count();                                      // 统计数量
    void deleteById(ID id);                            // 根据ID删除
    void delete(T entity);                             // 删除实体
    void deleteAll(Iterable<? extends T> entities);    // 批量删除
    void deleteAll();                                  // 删除所有
}

# 1.2、PagingAndSortingRepository

增加了分页和排序功能:

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

# 1.3、JpaRepository(推荐使用)

这是我们最常用的接口,它整合了上面所有功能,并增加了JPA特有的方法:

public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, 
                                               ListPagingAndSortingRepository<T, ID>,
                                               QueryByExampleExecutor<T> {
    void flush();                                      // 刷新持久化上下文
    <S extends T> S saveAndFlush(S entity);           // 保存并刷新
    <S extends T> List<S> saveAllAndFlush(Iterable<S> entities);  // 批量保存并刷新
    void deleteAllInBatch(Iterable<T> entities);       // 批量删除(一条SQL)
    void deleteAllByIdInBatch(Iterable<ID> ids);       // 批量删除(一条SQL)
    void deleteAllInBatch();                           // 删除所有(一条SQL)
    T getOne(ID id);                                   // 获取引用(懒加载)
    T getReferenceById(ID id);                         // 获取引用(推荐)
    <S extends T> List<S> findAll(Example<S> example); // 按示例查询
    <S extends T> List<S> findAll(Example<S> example, Sort sort);  // 按示例排序查询
}

# 2、方法命名查询

Spring Data JPA最强大的功能之一就是根据方法名自动生成查询。

# 2.1、基本规则

方法名由以下部分组成:

  • 前缀:findBy、getBy、queryBy、readBy、countBy、deleteBy等
  • 属性名:实体类的属性名(首字母大写)
  • 条件关键字:And、Or、Between等
  • 返回类型:Optional<T>、List<T>、Stream<T>等

# 2.2、常用关键字

public interface UserRepository extends JpaRepository<User, Long> {
    
    // 等于(Equals,可省略)
    User findByUsername(String username);
    
    // 不等于(Not)
    List<User> findByUsernameNot(String username);
    
    // Like(模糊查询)
    List<User> findByUsernameLike(String pattern);      // 需要传 "%pattern%"
    List<User> findByUsernameContaining(String keyword); // 自动加 %%
    List<User> findByUsernameStartingWith(String prefix); // 前缀匹配
    List<User> findByUsernameEndingWith(String suffix);   // 后缀匹配
    
    // 大小比较
    List<User> findByAgeGreaterThan(Integer age);        // >
    List<User> findByAgeGreaterThanEqual(Integer age);   // >=
    List<User> findByAgeLessThan(Integer age);           // <
    List<User> findByAgeLessThanEqual(Integer age);      // <=
    List<User> findByAgeBetween(Integer start, Integer end);  // BETWEEN
    
    // In和NotIn
    List<User> findByAgeIn(Collection<Integer> ages);
    List<User> findByAgeNotIn(Collection<Integer> ages);
    
    // IsNull和IsNotNull
    List<User> findByEmailIsNull();
    List<User> findByEmailIsNotNull();
    
    // True和False(针对Boolean类型)
    List<User> findByActiveTrue();
    List<User> findByActiveFalse();
    
    // IgnoreCase(忽略大小写)
    User findByUsernameIgnoreCase(String username);
    
    // OrderBy(排序)
    List<User> findByAgeOrderByUsernameAsc(Integer age);
    List<User> findByAgeOrderByUsernameDesc(Integer age);
    
    // And和Or(组合条件)
    User findByUsernameAndEmail(String username, String email);
    List<User> findByUsernameOrEmail(String username, String email);
    
    // Distinct(去重)
    List<User> findDistinctByAge(Integer age);
    
    // Top和First(限制结果数量)
    User findFirstByOrderByCreateTimeDesc();  // 最新的一条
    List<User> findTop10ByOrderByAgeDesc();   // 年龄最大的10个
    
    // Exists(是否存在)
    boolean existsByUsername(String username);
    
    // Count(统计数量)
    long countByAge(Integer age);
    
    // Delete(删除)
    long deleteByAge(Integer age);  // 返回删除的数量
    List<User> removeByAge(Integer age);  // 返回被删除的实体
}

# 2.3、复杂示例

public interface UserRepository extends JpaRepository<User, Long> {
    
    // 查询年龄在18-30之间,用户名包含"zhang",按创建时间倒序
    List<User> findByAgeBetweenAndUsernameContainingOrderByCreateTimeDesc(
        Integer startAge, Integer endAge, String keyword
    );
    
    // 查询邮箱不为空且年龄大于18的用户,返回前20条
    List<User> findTop20ByEmailIsNotNullAndAgeGreaterThanOrderByCreateTimeDesc(Integer age);
    
    // 统计用户名包含指定关键字的用户数量
    long countByUsernameContaining(String keyword);
}

# 3、@Query自定义查询

当方法名查询无法满足需求时,可以使用@Query注解自定义查询。

# 3.1、JPQL查询

JPQL(Java Persistence Query Language)是面向对象的查询语言:

public interface UserRepository extends JpaRepository<User, Long> {
    
    // 基本JPQL查询
    @Query("SELECT u FROM User u WHERE u.age > ?1")
    List<User> findUsersOlderThan(Integer age);
    
    // 命名参数(推荐)
    @Query("SELECT u FROM User u WHERE u.username = :username AND u.email = :email")
    Optional<User> findByUsernameAndEmail(
        @Param("username") String username, 
        @Param("email") String email
    );
    
    // 投影查询(只查询部分字段)
    @Query("SELECT u.username, u.email FROM User u WHERE u.age > :age")
    List<Object[]> findUserInfoByAge(@Param("age") Integer age);
    
    // 使用DTO接收结果
    @Query("SELECT new com.example.dto.UserDTO(u.username, u.email) FROM User u WHERE u.age > :age")
    List<UserDTO> findUserDTOByAge(@Param("age") Integer age);
    
    // 关联查询
    @Query("SELECT u FROM User u LEFT JOIN FETCH u.profile WHERE u.id = :id")
    Optional<User> findUserWithProfile(@Param("id") Long id);
    
    // 分组聚合
    @Query("SELECT u.age, COUNT(u) FROM User u GROUP BY u.age")
    List<Object[]> countByAge();
    
    // 统计查询
    @Query("SELECT COUNT(u) FROM User u WHERE u.age BETWEEN :start AND :end")
    long countByAgeBetween(@Param("start") Integer start, @Param("end") Integer end);
    
    // 更新操作
    @Modifying
    @Query("UPDATE User u SET u.email = :email WHERE u.id = :id")
    int updateEmailById(@Param("id") Long id, @Param("email") String email);
    
    // 删除操作
    @Modifying
    @Query("DELETE FROM User u WHERE u.age < :age")
    int deleteByAgeLessThan(@Param("age") Integer age);
}

注意:

  • 更新和删除操作必须加@Modifying注解
  • 更新和删除操作需要在Service层加@Transactional注解
  • JPQL中的类名是实体类名,不是表名

# 3.2、原生SQL查询

有时候JPQL无法满足需求,可以使用原生SQL:

public interface UserRepository extends JpaRepository<User, Long> {
    
    // 原生SQL查询
    @Query(value = "SELECT * FROM t_user WHERE age > ?1", nativeQuery = true)
    List<User> findUsersOlderThanNative(Integer age);
    
    // 使用命名参数
    @Query(value = "SELECT * FROM t_user WHERE username = :username", nativeQuery = true)
    Optional<User> findByUsernameNative(@Param("username") String username);
    
    // 复杂统计查询
    @Query(value = """
        SELECT age, COUNT(*) as count 
        FROM t_user 
        GROUP BY age 
        HAVING COUNT(*) > 1
        ORDER BY age
        """, nativeQuery = true)
    List<Object[]> findAgeStatistics();
    
    // 原生SQL更新
    @Modifying
    @Query(value = "UPDATE t_user SET email = :email WHERE id = :id", nativeQuery = true)
    int updateEmailByIdNative(@Param("id") Long id, @Param("email") String email);
}

# 4、分页和排序

# 4.1、排序(Sort)

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public List<User> getUsersSorted() {
        // 单字段排序
        Sort sort = Sort.by("age").descending();
        
        // 多字段排序
        Sort multiSort = Sort.by("age").descending()
                             .and(Sort.by("username").ascending());
        
        // 使用Order构建
        Sort orderSort = Sort.by(
            Sort.Order.desc("age"),
            Sort.Order.asc("username")
        );
        
        return userRepository.findAll(sort);
    }
}

# 4.2、分页(Pageable)

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public Page<User> getUsersPaged(int pageNum, int pageSize) {
        // 创建分页请求(页码从0开始)
        Pageable pageable = PageRequest.of(pageNum, pageSize);
        
        // 分页+排序
        Pageable pageableWithSort = PageRequest.of(
            pageNum, 
            pageSize, 
            Sort.by("createTime").descending()
        );
        
        // 执行分页查询
        Page<User> page = userRepository.findAll(pageable);
        
        // 获取分页信息
        long totalElements = page.getTotalElements();  // 总记录数
        int totalPages = page.getTotalPages();         // 总页数
        int currentPage = page.getNumber();            // 当前页码
        int pageSize = page.getSize();                 // 每页大小
        List<User> content = page.getContent();        // 当前页数据
        boolean hasNext = page.hasNext();              // 是否有下一页
        boolean hasPrevious = page.hasPrevious();      // 是否有上一页
        
        return page;
    }
    
    // 自定义查询的分页
    public Page<User> searchUsers(String keyword, int pageNum, int pageSize) {
        Pageable pageable = PageRequest.of(pageNum, pageSize);
        return userRepository.findByUsernameContaining(keyword, pageable);
    }
}

Repository中的分页方法:

public interface UserRepository extends JpaRepository<User, Long> {
    
    // 方法名查询 + 分页
    Page<User> findByUsernameContaining(String keyword, Pageable pageable);
    
    // @Query查询 + 分页
    @Query("SELECT u FROM User u WHERE u.age > :age")
    Page<User> findUsersOlderThan(@Param("age") Integer age, Pageable pageable);
    
    // 如果只需要列表不需要总数统计,可以使用Slice
    Slice<User> findByAge(Integer age, Pageable pageable);
}

Page和Slice的区别:

  • Page:会执行count查询获取总数,适合需要显示总页数的场景
  • Slice:不执行count查询,只知道是否有下一页,性能更好

# 六、Specifications动态查询

当查询条件是动态的(用户可能选择不同的搜索条件),方法名查询和@Query都不够灵活。这时候就需要使用Specifications。

# 1、启用Specifications

让Repository继承JpaSpecificationExecutor接口:

public interface UserRepository extends JpaRepository<User, Long>, 
                                        JpaSpecificationExecutor<User> {
}

# 2、基本使用

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public List<User> searchUsers(String username, Integer minAge, Integer maxAge) {
        // 创建Specification
        Specification<User> spec = (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            
            // 用户名模糊查询
            if (username != null && !username.isEmpty()) {
                predicates.add(
                    criteriaBuilder.like(root.get("username"), "%" + username + "%")
                );
            }
            
            // 年龄范围查询
            if (minAge != null) {
                predicates.add(
                    criteriaBuilder.greaterThanOrEqualTo(root.get("age"), minAge)
                );
            }
            if (maxAge != null) {
                predicates.add(
                    criteriaBuilder.lessThanOrEqualTo(root.get("age"), maxAge)
                );
            }
            
            // 组合所有条件(AND)
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
        
        return userRepository.findAll(spec);
    }
}

# 3、封装Specification工具类

为了代码复用,可以封装一个工具类:

/**
 * Specification工具类
 */
public class SpecificationBuilder<T> {
    
    private List<Specification<T>> specifications = new ArrayList<>();
    
    /**
     * 添加等于条件
     */
    public SpecificationBuilder<T> equal(String field, Object value) {
        if (value != null) {
            specifications.add((root, query, cb) -> 
                cb.equal(root.get(field), value)
            );
        }
        return this;
    }
    
    /**
     * 添加模糊查询条件
     */
    public SpecificationBuilder<T> like(String field, String value) {
        if (value != null && !value.isEmpty()) {
            specifications.add((root, query, cb) -> 
                cb.like(root.get(field), "%" + value + "%")
            );
        }
        return this;
    }
    
    /**
     * 添加大于等于条件
     */
    public SpecificationBuilder<T> greaterThanOrEqual(String field, Comparable value) {
        if (value != null) {
            specifications.add((root, query, cb) -> 
                cb.greaterThanOrEqualTo(root.get(field), value)
            );
        }
        return this;
    }
    
    /**
     * 添加小于等于条件
     */
    public SpecificationBuilder<T> lessThanOrEqual(String field, Comparable value) {
        if (value != null) {
            specifications.add((root, query, cb) -> 
                cb.lessThanOrEqualTo(root.get(field), value)
            );
        }
        return this;
    }
    
    /**
     * 添加In条件
     */
    public SpecificationBuilder<T> in(String field, Collection<?> values) {
        if (values != null && !values.isEmpty()) {
            specifications.add((root, query, cb) -> 
                root.get(field).in(values)
            );
        }
        return this;
    }
    
    /**
     * 添加Between条件
     */
    public SpecificationBuilder<T> between(String field, Comparable start, Comparable end) {
        if (start != null && end != null) {
            specifications.add((root, query, cb) -> 
                cb.between(root.get(field), start, end)
            );
        }
        return this;
    }
    
    /**
     * 添加IsNull条件
     */
    public SpecificationBuilder<T> isNull(String field) {
        specifications.add((root, query, cb) -> 
            cb.isNull(root.get(field))
        );
        return this;
    }
    
    /**
     * 添加IsNotNull条件
     */
    public SpecificationBuilder<T> isNotNull(String field) {
        specifications.add((root, query, cb) -> 
            cb.isNotNull(root.get(field))
        );
        return this;
    }
    
    /**
     * 构建最终的Specification(AND条件)
     */
    public Specification<T> build() {
        return (root, query, cb) -> {
            if (specifications.isEmpty()) {
                return cb.conjunction();  // 返回恒真条件
            }
            Predicate[] predicates = specifications.stream()
                .map(spec -> spec.toPredicate(root, query, cb))
                .toArray(Predicate[]::new);
            return cb.and(predicates);
        };
    }
    
    /**
     * 构建最终的Specification(OR条件)
     */
    public Specification<T> buildOr() {
        return (root, query, cb) -> {
            if (specifications.isEmpty()) {
                return cb.conjunction();
            }
            Predicate[] predicates = specifications.stream()
                .map(spec -> spec.toPredicate(root, query, cb))
                .toArray(Predicate[]::new);
            return cb.or(predicates);
        };
    }
}

使用示例:

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public List<User> searchUsers(String username, Integer minAge, Integer maxAge) {
        Specification<User> spec = new SpecificationBuilder<User>()
            .like("username", username)
            .greaterThanOrEqual("age", minAge)
            .lessThanOrEqual("age", maxAge)
            .build();
        
        return userRepository.findAll(spec);
    }
    
    // 分页+排序+动态查询
    public Page<User> searchUsersPaged(String username, Integer minAge, 
                                       int pageNum, int pageSize) {
        Specification<User> spec = new SpecificationBuilder<User>()
            .like("username", username)
            .greaterThanOrEqual("age", minAge)
            .build();
        
        Pageable pageable = PageRequest.of(pageNum, pageSize, 
                                          Sort.by("createTime").descending());
        
        return userRepository.findAll(spec, pageable);
    }
}

# 七、事务管理

# 1、声明式事务

Spring Data JPA的事务管理非常简单,只需在Service方法上加@Transactional注解:

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 创建用户(事务)
     */
    @Transactional
    public User createUser(User user) {
        return userRepository.save(user);
    }
    
    /**
     * 批量创建用户(事务)
     */
    @Transactional
    public List<User> createUsers(List<User> users) {
        return userRepository.saveAll(users);
    }
    
    /**
     * 转账示例(事务)
     */
    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        User from = userRepository.findById(fromId)
            .orElseThrow(() -> new RuntimeException("转出用户不存在"));
        User to = userRepository.findById(toId)
            .orElseThrow(() -> new RuntimeException("转入用户不存在"));
        
        // 扣款
        from.setBalance(from.getBalance().subtract(amount));
        userRepository.save(from);
        
        // 如果这里抛异常,上面的扣款会回滚
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new RuntimeException("转账金额必须大于0");
        }
        
        // 加款
        to.setBalance(to.getBalance().add(amount));
        userRepository.save(to);
    }
}

# 2、事务属性

@Transactional注解有很多属性可以配置:

@Transactional(
    // 事务传播行为
    propagation = Propagation.REQUIRED,
    
    // 事务隔离级别
    isolation = Isolation.DEFAULT,
    
    // 超时时间(秒)
    timeout = 30,
    
    // 是否只读事务(只读事务性能更好)
    readOnly = false,
    
    // 遇到哪些异常回滚(默认RuntimeException和Error)
    rollbackFor = {Exception.class},
    
    // 遇到哪些异常不回滚
    noRollbackFor = {IllegalArgumentException.class}
)
public void someMethod() {
    // ...
}

# 3、事务传播行为

@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private LogService logService;
    
    /**
     * REQUIRED(默认):如果当前有事务,加入;没有就新建
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder() {
        // 保存订单
        orderRepository.save(new Order());
        
        // 记录日志(加入当前事务)
        logService.logRequired("订单创建");
    }
    
    /**
     * REQUIRES_NEW:总是新建事务,如果当前有事务,挂起
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createOrderWithNewTransaction() {
        orderRepository.save(new Order());
        
        // 日志在新事务中(即使订单创建失败,日志也会保存)
        logService.logRequiresNew("订单创建");
    }
    
    /**
     * NESTED:嵌套事务,可以部分回滚
     */
    @Transactional(propagation = Propagation.NESTED)
    public void createOrderNested() {
        orderRepository.save(new Order());
        
        try {
            // 尝试发送通知(失败不影响订单)
            logService.logNested("发送通知");
        } catch (Exception e) {
            // 嵌套事务回滚,但不影响外层事务
        }
    }
}

@Service
public class LogService {
    
    @Autowired
    private LogRepository logRepository;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void logRequired(String message) {
        logRepository.save(new Log(message));
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logRequiresNew(String message) {
        logRepository.save(new Log(message));
    }
    
    @Transactional(propagation = Propagation.NESTED)
    public void logNested(String message) {
        logRepository.save(new Log(message));
    }
}

# 4、只读事务

对于查询操作,使用只读事务可以提升性能:

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 只读事务
     */
    @Transactional(readOnly = true)
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    /**
     * 分页查询也应该用只读事务
     */
    @Transactional(readOnly = true)
    public Page<User> getUsersPaged(int pageNum, int pageSize) {
        Pageable pageable = PageRequest.of(pageNum, pageSize);
        return userRepository.findAll(pageable);
    }
}

# 八、审计功能

审计功能可以自动记录创建时间、修改时间、创建人、修改人等信息。

# 1、启用审计

在配置类上加@EnableJpaAuditing注解:

@SpringBootApplication
@EnableJpaAuditing
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

# 2、使用审计注解

@Entity
@Table(name = "t_user")
@EntityListeners(AuditingEntityListener.class)  // 启用审计监听器
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    /**
     * 创建时间(自动设置)
     */
    @CreatedDate
    @Column(nullable = false, updatable = false)
    private LocalDateTime createTime;
    
    /**
     * 修改时间(自动更新)
     */
    @LastModifiedDate
    @Column(nullable = false)
    private LocalDateTime updateTime;
    
    /**
     * 创建人(自动设置)
     */
    @CreatedBy
    @Column(nullable = false, updatable = false, length = 50)
    private String createdBy;
    
    /**
     * 修改人(自动更新)
     */
    @LastModifiedBy
    @Column(nullable = false, length = 50)
    private String lastModifiedBy;
    
    // getter/setter...
}

# 3、提供审计人信息

创建一个AuditorAware实现类,告诉JPA当前用户是谁:

@Component
public class AuditorAwareImpl implements AuditorAware<String> {
    
    @Override
    public Optional<String> getCurrentAuditor() {
        // 从Spring Security获取当前用户
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        if (authentication == null || !authentication.isAuthenticated()) {
            return Optional.of("system");
        }
        
        return Optional.of(authentication.getName());
    }
}

如果项目中没有使用Spring Security,可以简化:

@Component
public class SimpleAuditorAware implements AuditorAware<String> {
    
    @Override
    public Optional<String> getCurrentAuditor() {
        // 这里可以从ThreadLocal、Session等地方获取当前用户
        // 示例:返回固定值
        return Optional.of("admin");
    }
}

# 4、抽取基类

为了避免每个实体都重复定义审计字段,可以创建一个基类:

/**
 * 审计基类
 */
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
    
    @CreatedDate
    @Column(nullable = false, updatable = false)
    private LocalDateTime createTime;
    
    @LastModifiedDate
    @Column(nullable = false)
    private LocalDateTime updateTime;
    
    @CreatedBy
    @Column(nullable = false, updatable = false, length = 50)
    private String createdBy;
    
    @LastModifiedBy
    @Column(nullable = false, length = 50)
    private String lastModifiedBy;
    
    // getter/setter...
}

实体类只需继承基类:

@Entity
@Table(name = "t_user")
public class User extends BaseEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    private String email;
    
    // getter/setter...
}

# 九、性能优化

# 1、N+1查询问题

这是使用ORM框架最常遇到的性能问题。

问题示例:

// 查询所有部门
List<Department> departments = departmentRepository.findAll();

// 遍历部门,访问员工(会触发N次查询)
for (Department dept : departments) {
    System.out.println(dept.getName());
    for (Employee emp : dept.getEmployees()) {  // 每次都会查询数据库!
        System.out.println(emp.getName());
    }
}

这会产生1(查部门)+ N(查N个部门的员工)次查询。

解决方案1:使用JOIN FETCH

@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
    
    // 使用JOIN FETCH一次性加载部门和员工
    @Query("SELECT d FROM Department d LEFT JOIN FETCH d.employees")
    List<Department> findAllWithEmployees();
}

解决方案2:使用@EntityGraph

@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
    
    @EntityGraph(attributePaths = {"employees"})
    List<Department> findAll();
}

解决方案3:使用@NamedEntityGraph

在实体类上定义:

@Entity
@Table(name = "t_department")
@NamedEntityGraph(
    name = "Department.withEmployees",
    attributeNodes = @NamedAttributeNode("employees")
)
public class Department {
    // ...
}

在Repository中使用:

@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
    
    @EntityGraph(value = "Department.withEmployees")
    List<Department> findAll();
}

# 2、批量操作优化

批量插入:

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 批量插入(每批1000条)
     */
    @Transactional
    public void batchInsert(List<User> users) {
        int batchSize = 1000;
        for (int i = 0; i < users.size(); i++) {
            userRepository.save(users.get(i));
            
            if (i % batchSize == 0 && i > 0) {
                // 每1000条刷新一次
                userRepository.flush();
            }
        }
    }
    
    /**
     * 使用saveAll(更推荐)
     */
    @Transactional
    public void batchInsertSaveAll(List<User> users) {
        userRepository.saveAll(users);
        userRepository.flush();
    }
}

配置Hibernate批量插入:

spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          batch_size: 1000  # 批量大小
        order_inserts: true  # 对插入语句排序
        order_updates: true  # 对更新语句排序

批量删除:

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 批量删除(一条SQL)
     */
    @Transactional
    public void batchDelete(List<Long> ids) {
        userRepository.deleteAllByIdInBatch(ids);
    }
}

# 3、投影查询

当只需要部分字段时,使用投影查询可以减少数据传输量:

接口投影:

/**
 * 投影接口
 */
public interface UserProjection {
    String getUsername();
    String getEmail();
}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    // 返回投影
    List<UserProjection> findAllProjectedBy();
    
    UserProjection findProjectedById(Long id);
}

类投影:

/**
 * DTO类
 */
public class UserDTO {
    private String username;
    private String email;
    
    public UserDTO(String username, String email) {
        this.username = username;
        this.email = email;
    }
    
    // getter/setter...
}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    @Query("SELECT new com.example.dto.UserDTO(u.username, u.email) FROM User u")
    List<UserDTO> findAllDTO();
}

# 4、缓存

一级缓存(Session缓存):

JPA默认开启,同一个事务内多次查询同一个实体,只会查询一次数据库。

二级缓存(SessionFactory缓存):

需要手动配置,可以跨事务共享。

添加依赖:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
</dependency>

配置:

spring:
  jpa:
    properties:
      hibernate:
        cache:
          use_second_level_cache: true
          region:
            factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory

在实体上启用缓存:

@Entity
@Table(name = "t_user")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
    // ...
}

# 5、懒加载优化

问题:在Controller层访问懒加载的关联对象,会抛LazyInitializationException异常。

解决方案1:在Service层处理

@Service
public class DepartmentService {
    
    @Autowired
    private DepartmentRepository departmentRepository;
    
    @Transactional(readOnly = true)
    public Department getDepartmentWithEmployees(Long id) {
        Department dept = departmentRepository.findById(id).orElseThrow();
        // 在事务内访问懒加载对象,触发加载
        dept.getEmployees().size();
        return dept;
    }
}

解决方案2:使用DTO

@Service
public class DepartmentService {
    
    @Autowired
    private DepartmentRepository departmentRepository;
    
    @Transactional(readOnly = true)
    public DepartmentDTO getDepartment(Long id) {
        Department dept = departmentRepository.findById(id).orElseThrow();
        // 转换为DTO
        return new DepartmentDTO(dept);
    }
}

解决方案3:使用OSIV(不推荐)

spring:
  jpa:
    open-in-view: true  # 开启OSIV(默认开启)

OSIV会在整个请求周期内保持Session,虽然方便,但可能导致性能问题和意外的查询。

# 十、最佳实践

# 1、实体设计原则

  • 使用包装类型:Long id而不是long id,避免0值的歧义
  • 重写equals和hashCode:特别是在Set和Map中使用实体时
  • 避免双向关联:除非真的需要,单向关联更简单
  • 谨慎使用级联:特别是CascadeType.REMOVE,容易误删数据
  • 使用懒加载:默认使用LAZY,需要时再用EAGER

# 2、Repository设计原则

  • 继承JpaRepository:功能最全面
  • 方法命名要规范:遵循Spring Data JPA的命名规则
  • 复杂查询用@Query:别让方法名太长
  • 动态查询用Specification:灵活且类型安全

# 3、Service设计原则

  • 加事务注解:写操作必须加@Transactional
  • 只读事务:查询操作加@Transactional(readOnly = true)
  • 避免事务太大:拆分成小事务,提高并发性
  • 处理懒加载:在事务内访问懒加载对象

# 4、性能优化建议

  • 避免N+1查询:使用JOIN FETCH或EntityGraph
  • 使用批量操作:批量插入、批量删除
  • 使用投影查询:只查询需要的字段
  • 合理使用索引:在常用查询字段上建索引
  • 监控SQL:开发环境开启show-sql,生产环境使用慢查询日志

# 5、常见错误

错误1:在循环中save

// 错误示例
for (User user : users) {
    userRepository.save(user);  // 每次都会发SQL
}

// 正确示例
userRepository.saveAll(users);  // 批量保存

错误2:忘记加@Transactional

// 错误示例
public void updateUser(Long id, String email) {
    User user = userRepository.findById(id).orElseThrow();
    user.setEmail(email);
    // 没有save,更新不会生效!
}

// 正确示例
@Transactional
public void updateUser(Long id, String email) {
    User user = userRepository.findById(id).orElseThrow();
    user.setEmail(email);
    // 事务提交时自动更新
}

错误3:LazyInitializationException

// 错误示例
public Department getDepartment(Long id) {
    return departmentRepository.findById(id).orElseThrow();
    // 在Controller访问employees会报错
}

// 正确示例
@Transactional(readOnly = true)
public Department getDepartment(Long id) {
    Department dept = departmentRepository.findById(id).orElseThrow();
    dept.getEmployees().size();  // 在事务内触发加载
    return dept;
}

# 十一、总结

JPA和Spring Data JPA大大简化了数据访问层的开发,让我们可以用面向对象的方式操作数据库。通过本文,你应该掌握了:

  • JPA基础概念:实体映射、关系映射、生命周期等
  • Repository使用:方法命名查询、@Query、Specifications
  • 分页排序:Pageable和Sort的使用
  • 事务管理:声明式事务、事务传播行为
  • 审计功能:自动记录创建时间、修改时间等
  • 性能优化:避免N+1查询、批量操作、缓存等

掌握了这些知识,你就可以在项目中游刃有余地使用JPA了。但记住,JPA不是银弹,对于复杂的SQL查询和批量操作,MyBatis可能是更好的选择。选择合适的工具,才能事半功倍。

祝你变得更强!

编辑 (opens new window)
#JPA#Spring Data JPA#Hibernate#ORM
上次更新: 2025/12/06
Thymeleaf 与 Spring 框架的集成教程
C语言指针二三事

← Thymeleaf 与 Spring 框架的集成教程 C语言指针二三事→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code 最佳实践(个人版)
08-01
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式