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 JPA | MyBatis |
|---|---|---|
| 理念 | 全自动 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 问题?
@JoinFetch(Hibernate 特有)@EntityGraph@Query JOIN FETCH- 批量查询
Q5: Hibernate 的 flush 模式?
- AUTO:默认,必要时刷新
- COMMIT:事务提交时才刷新
- ALWAYS:每个语句执行后都刷新
十五、下一章预告
下一章我们将学习 事务管理:
- @Transactional 用法
- 事务传播行为
- 隔离级别
- 嵌套事务