Skip to content

Spring Data JPA

一、JPA 概述

什么是 JPA

┌─────────────────────────────────────────────────────────────────┐
│                         Java Persistence API                      │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  JPA 是 Java EE 的持久化 API 规范                                 │
│                                                                  │
│  ORM 框架要实现 JPA 规范:                                        │
│  ┌──────────────┐                                               │
│  │   JPA 规范   │  ← Hibernate (jboss)                         │
│  │   (接口)     │  ← EclipseLink (eclipse)                     │
│  │              │  ← OpenJPA (apache)                          │
│  └──────────────┘                                               │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Spring Data JPA

Spring Data JPA = JPA + Spring Data 特性

┌─────────────────────────────────────────────────────────────────┐
│                        Spring Data JPA                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐        │
│  │  Repository │ ──▶ │ CrudRepository│ ──▶ │ JpaRepository│     │
│  └─────────────┘     └─────────────┘     └─────────────┘        │
│         │                                                       │
│         │ 自动实现                                              │
│         ▼                                                       │
│  ┌─────────────────────────────────────────────────────┐        │
│  │              JdbcTemplate / EntityManager            │        │
│  └─────────────────────────────────────────────────────┘        │
│         │                                                       │
│         ▼                                                       │
│  ┌─────────────────────────────────────────────────────┐        │
│  │              Hibernate / EclipseLink                │        │
│  └─────────────────────────────────────────────────────┘        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

二、快速开始

Maven 依赖

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

配置

yaml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  
  jpa:
    hibernate:
      ddl-auto: update    # none/validate/update/create/create-drop
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.MySQL8Dialect

实体类

java
@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, length = 50)
    private String name;
    
    @Column(unique = true, length = 100)
    private String email;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    // getter/setter
}

Repository 接口

java
public interface UserRepository extends JpaRepository<User, Long> {
    
    // 方法名查询
    User findByEmail(String email);
    
    List<User> findByNameContaining(String name);
    
    List<User> findByAgeGreaterThanEqual(int age);
    
    // 多条件查询
    Optional<User> findByNameAndEmail(String name, String email);
    
    // 分页
    Page<User> findByNameContaining(String name, Pageable pageable);
    
    // 排序
    List<User> findByNameContainingOrderByCreatedAtDesc(String name);
}

使用

java
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public User save(User user) {
        return userRepository.save(user);
    }
    
    public Optional<User> findById(Long id) {
        return userRepository.findById(id);
    }
    
    public List<User> findAll() {
        return userRepository.findAll();
    }
    
    public void delete(Long id) {
        userRepository.deleteById(id);
    }
}

三、Repository 层级

┌─────────────────────────────────────────────────────────────────┐
│                        Repository 层级                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Repository<T, ID>                                              │
│      │ 标记接口,空方法                                          │
│      ▼                                                           │
│  CrudRepository<T, ID>                                          │
│      │ save, findById, existsById, count, delete...            │
│      ▼                                                           │
│  PagingAndSortingRepository<T, ID>                              │
│      │ findAll(Pageable), findAll(Sort)                        │
│      ▼                                                           │
│  JpaRepository<T, ID>                                          │
│      │ flush, saveAndFlush, deleteInBatch...                   │
│      ▼                                                           │
│  自定义 Repository                                               │
│      │ 自定义查询方法                                           │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

四、方法命名查询

语法

findBy + 属性名 + [条件运算符] + [连接运算符]

┌─────────────────────────────────────────────────────────────────┐
│                     方法命名规则                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  findByName(String name)              → WHERE name = ?          │
│  findByNameIs(String name)            → WHERE name = ?          │
│  findByNameEquals(String name)        → WHERE name = ?          │
│  findByNameIsNull()                   → WHERE name IS NULL      │
│  findByNameIsNotNull()                → WHERE name IS NOT NULL  │
│  findByNameLike(String name)          → WHERE name LIKE ?       │
│  findByNameNotLike(String name)       → WHERE name NOT LIKE ?   │
│  findByAgeGreaterThan(int age)        → WHERE age > ?           │
│  findByAgeGreaterThanEqual(int age)   → WHERE age >= ?          │
│  findByAgeLessThan(int age)           → WHERE age < ?           │
│  findByAgeLessThanEqual(int age)      → WHERE age <= ?          │
│  findByAgeBetween(int min, int max)   → WHERE age BETWEEN ? AND ?│
│  findByNameContaining(String str)      → WHERE name LIKE '%?%'   │
│  findByNameStartingWith(String str)   → WHERE name LIKE '?%'    │
│  findByNameEndingWith(String str)     → WHERE name LIKE '%?'    │
│  findByNameIgnoreCase(String name)   → LOWER(name) = LOWER(?)  │
│  findByActiveTrue()                   → WHERE active = true     │
│  findByActiveFalse()                  → WHERE active = false    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

多条件查询

java
// And
User findByNameAndEmail(String name, String email);
// WHERE name = ? AND email = ?

// Or
List<User> findByNameOrEmail(String name, String email);
// WHERE name = ? OR email = ?

// 复杂组合
List<User> findByNameOrEmailAndAgeGreaterThanEqual(
    String name, String email, int minAge);
// WHERE (name = ? OR email = ?) AND age >= ?

分页和排序

java
// 分页
Page<User> findByNameContaining(String name, Pageable pageable);

// 排序
List<User> findByNameContainingOrderByCreatedAtDesc(String name);
List<User> findByNameContainingOrderByCreatedAtAsc(String name);

// 复杂排序
List<User> findByNameContaining(String name, Sort sort);
userRepository.findByNameContaining("张", 
    Sort.by(Sort.Direction.DESC, "age").and(Sort.by("createdAt")));

五、分页查询

Pageable 参数

java
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public Page<User> findPage(int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findAll(pageable);
    }
    
    public Page<User> findPageSorted(int page, int size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
        return userRepository.findAll(pageable);
    }
    
    public Page<User> findByCondition(String name, int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return userRepository.findByNameContaining(name, pageable);
    }
}

返回 Page

java
public Page<User> findByCondition(String name, int page, int size) {
    Pageable pageable = PageRequest.of(page, size);
    Page<User> userPage = userRepository.findByNameContaining(name, pageable);
    
    // 分页信息
    int totalPages = userPage.getTotalPages();     // 总页数
    long totalElements = userPage.getTotalElements(); // 总记录数
    int number = userPage.getNumber();              // 当前页码
    int size = userPage.getSize();                  // 每页大小
    boolean first = userPage.isFirst();             // 是否第一页
    boolean last = userPage.isLast();               // 是否最后一页
    
    // 数据
    List<User> content = userPage.getContent();
    
    return userPage;
}

前端返回

java
@GetMapping("/users")
public Map<String, Object> getUsers(
    @RequestParam(defaultValue = "0") int page,
    @RequestParam(defaultValue = "10") int size,
    @RequestParam(required = false) String name
) {
    Page<User> pageResult;
    if (name != null) {
        pageResult = userService.findByCondition(name, page, size);
    } else {
        pageResult = userService.findPage(page, size);
    }
    
    return Map.of(
        "content", pageResult.getContent(),
        "totalPages", pageResult.getTotalPages(),
        "totalElements", pageResult.getTotalElements(),
        "page", pageResult.getNumber(),
        "size", pageResult.getSize()
    );
}

六、自定义查询

@Query 注解

java
public interface UserRepository extends JpaRepository<User, Long> {
    
    // JPQL 查询
    @Query("SELECT u FROM User u WHERE u.name = ?1")
    User findByNameQuery(String name);
    
    // 位置参数
    @Query("SELECT u FROM User u WHERE u.name = ?1 AND u.email = ?2")
    User findByNameAndEmail(String name, String email);
    
    // 命名参数
    @Query("SELECT u FROM User u WHERE u.name = :name AND u.email = :email")
    User findByNameAndEmailParam(@Param("name") String name, @Param("email") String email);
    
    // 原生 SQL
    @Query(value = "SELECT * FROM users WHERE name = ?1", nativeQuery = true)
    User findByNameNative(String name);
    
    // 原生 SQL + 分页
    @Query(value = "SELECT * FROM users WHERE name LIKE %?1%", 
           countQuery = "SELECT COUNT(*) FROM users WHERE name LIKE %?1%",
           nativeQuery = true)
    Page<User> findByNameLike(String name, Pageable pageable);
}

@Modifying 更新

java
public interface UserRepository extends JpaRepository<User, Long> {
    
    // 更新操作
    @Modifying
    @Query("UPDATE User u SET u.name = :name WHERE u.id = :id")
    int updateName(@Param("id") Long id, @Param("name") String name);
    
    // 删除操作
    @Modifying
    @Query("DELETE FROM User u WHERE u.active = false")
    int deleteInactiveUsers();
    
    // 批量更新
    @Modifying
    @Query("UPDATE User u SET u.age = u.age + 1 WHERE u.active = true")
    int incrementAgeForActiveUsers();
}

七、Entity 映射注解

常用注解

java
@Entity
@Table(name = "users", indexes = {
    @Index(name = "idx_name", columnList = "name"),
    @Index(name = "idx_email", columnList = "email")
})
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(
        name = "user_name",           // 列名
        nullable = false,             // 非空
        unique = true,                // 唯一
        length = 50,                 // 长度
        precision = 10,               // 精度(BigDecimal)
        scale = 2,                    // 小数位
        insertable = true,            // 是否插入
        updatable = false             // 是否更新
    )
    private String name;
    
    // 字符串类型
    @Column(columnDefinition = "VARCHAR(100) DEFAULT '匿名'")
    private String nickname;
    
    // 枚举类型
    @Enumerated(EnumType.STRING)
    @Column(name = "status")
    private UserStatus status;
    
    // 大字段
    @Lob
    @Column(columnDefinition = "TEXT")
    private String description;
    
    // 瞬时字段(不持久化)
    @Transient
    private String tempField;
    
    // 时间类型
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created_at")
    private Date createdAt;
    
    // 或使用 Java 8 时间类型(推荐)
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
}

枚举映射

java
public enum UserStatus {
    ACTIVE,      // 正常
    INACTIVE,    // 禁用
    DELETED      // 已删除
}

// EnumType.STRING 存储字符串
// EnumType.ORDINAL 存储序号

八、一对一关系

主表侧

java
@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column
    private String name;
    
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "profile_id")
    private UserProfile profile;
    
    // getter/setter
}

@Entity
@Table(name = "user_profiles")
public class UserProfile {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column
    private String bio;
    
    @Column
    private String avatar;
    
    @OneToOne(mappedBy = "profile", fetch = FetchType.LAZY)
    private User user;
    
    // getter/setter
}

操作

java
// 保存时级联保存
user.setProfile(profile);
userRepository.save(user);

// 查询
User user = userRepository.findById(id).orElseThrow();
UserProfile profile = user.getProfile();  // 懒加载

// 通过 profile 查 user
Optional<User> user = userRepository.findByProfileId(profileId);

九、一对多关系

常用配置

java
@Entity
@Table(name = "departments")
public class Department {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column
    private String name;
    
    @OneToMany(
        mappedBy = "department",
        fetch = FetchType.LAZY,
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<Employee> employees = new ArrayList<>();
    
    // 方便方法
    public void addEmployee(Employee emp) {
        employees.add(emp);
        emp.setDepartment(this);
    }
    
    public void removeEmployee(Employee emp) {
        employees.remove(emp);
        emp.setDepartment(null);
    }
}

@Entity
@Table(name = "employees")
public class Employee {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column
    private String name;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")
    private Department department;
}

Cascade 级联操作

类型说明
CascadeType.ALL所有操作级联
CascadeType.PERSIST保存级联
CascadeType.MERGE更新级联
CascadeType.REMOVE删除级联
CascadeType.REFRESH刷新级联

操作示例

java
// 保存部门时自动保存员工
Department dept = new Department();
dept.setName("技术部");
dept.addEmployee(new Employee("张三"));
dept.addEmployee(new Employee("李四"));
departmentRepository.save(dept);

// 查询部门时获取员工
Department dept = departmentRepository.findById(id).orElseThrow();
List<Employee> employees = dept.getEmployees();  // 可能触发 N+1

// 解决 N+1
@Query("SELECT d FROM Department d LEFT JOIN FETCH d.employees WHERE d.id = :id")
Department findByIdWithEmployees(@Param("id") Long id);

十、多对多关系

配置

java
@Entity
@Table(name = "students")
public class Student {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column
    private String name;
    
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private Set<Course> courses = new HashSet<>();
}

@Entity
@Table(name = "courses")
public class Course {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column
    private String name;
    
    @ManyToMany(mappedBy = "courses")  // 由 Student 维护关系
    private Set<Student> students = new HashSet<>();
}

操作

java
// 添加关系
Student student = studentRepository.findById(1L).orElseThrow();
Course course = courseRepository.findById(1L).orElseThrow();
student.getCourses().add(course);
studentRepository.save(student);

// 查询学生及其课程(避免 N+1)
@Query("SELECT s FROM Student s JOIN FETCH s.courses WHERE s.id = :id")
Student findByIdWithCourses(@Param("id") Long id);

十一、继承映射

MappedSuperclass

java
@MappedSuperclass
public abstract class BaseEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @Version
    private Long version;
}

@Entity
@Table(name = "users")
public class User extends BaseEntity {
    
    @Column
    private String name;
}

@Entity
@Table(name = "orders")
public class Order extends BaseEntity {
    
    @Column
    private BigDecimal amount;
}

@Inheritance

java
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type")
public abstract class Payment {
    // ...
}

@Entity
@DiscriminatorValue("CARD")
public class CardPayment extends Payment {
    private String cardNumber;
}

@Entity
@DiscriminatorValue("WECHAT")
public class WechatPayment extends Payment {
    private String openId;
}

十二、审计功能

@EnableJpaAuditing

java
@SpringBootApplication
@EnableJpaAuditing
public class Application { }

// 实体类
@Entity
@Table(name = "users")
@EntityListeners(AuditingEntityListener.class)
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @CreatedDate
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @CreatedBy
    @Column(name = "created_by")
    private String createdBy;
    
    @LastModifiedBy
    @Column(name = "updated_by")
    private String updatedBy;
}

自定义 Auditor

java
@Component
public class AuditConfig {
    
    @Bean
    public AuditorAware<String> auditorProvider() {
        return () -> {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (auth == null || !auth.isAuthenticated()) {
                return Optional.of("system");
            }
            return Optional.of(auth.getName());
        };
    }
}

十三、乐观锁

@Version

java
@Entity
public class Product {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column
    private String name;
    
    @Column
    private Integer stock;
    
    @Version
    private Long version;  // 乐观锁版本号
    
    // 减库存(乐观锁)
    public boolean reduceStock(int quantity) {
        if (this.stock >= quantity) {
            this.stock -= quantity;
            return true;
        }
        return false;
    }
}

并发控制

java
@Service
public class ProductService {
    
    @Autowired
    private ProductRepository productRepository;
    
    @Transactional
    public void reduceStock(Long productId, int quantity) {
        Optional<Product> opt = productRepository.findById(productId);
        Product product = opt.orElseThrow(() -> new RuntimeException("商品不存在"));
        
        if (!product.reduceStock(quantity)) {
            throw new RuntimeException("库存不足");
        }
        
        productRepository.save(product);
        // 如果版本冲突,Hibernate 会抛出 OptimisticLockException
    }
}

十四、面试高频问题

Q1: Spring Data JPA 和 MyBatis 区别?

对比Spring Data JPAMyBatis
理念全自动 ORM半自动 SQL
SQL自动生成手动编写
灵活度
性能一般更好控制
适用场景CRUD 为主复杂查询

Q2: save() 方法如何判断是 insert 还是 update?

  • 有主键值 → update
  • 无主键值或主键为 null → insert
  • 使用 @GeneratedValue 时,第一次 save 无 id,insert;之后有 id,update

Q3: @Transactional 在 JPA 中的注意事项?

  • 默认只对 RuntimeException 回滚
  • 需要在接口或实现类上标注
  • 内部方法调用(this.xxx())不会生效(代理问题)

Q4: 如何解决 N+1 问题?

  1. @JoinFetch(Hibernate 特有)
  2. @EntityGraph
  3. @Query JOIN FETCH
  4. 批量查询

Q5: Hibernate 的 flush 模式?

  • AUTO:默认,必要时刷新
  • COMMIT:事务提交时才刷新
  • ALWAYS:每个语句执行后都刷新

十五、下一章预告

下一章我们将学习 事务管理

  • @Transactional 用法
  • 事务传播行为
  • 隔离级别
  • 嵌套事务

基于 MIT 许可发布