Spring Data JPA 完全指南
一、概述
Spring Data JPA 是 Spring Data 项目的核心模块之一,它在 JPA(Java Persistence API)之上提供了一层抽象,极大地简化了数据访问层的开发。开发者只需定义接口,框架自动生成实现,消除了大量样板代码。
核心价值:
- 零实现类:只写接口,不写实现
- 方法名即查询:按命名规则自动生成 SQL
- 统一的 CRUD 抽象
- 内置分页、排序、审计支持
二、核心概念
2.1 JPA 与 Spring Data JPA 的关系
Hibernate (JPA 实现)
↑
JPA 规范 (javax.persistence / jakarta.persistence)
↑
Spring Data JPA (在 JPA 之上的 Repository 抽象)
- JPA 是规范(接口),定义了 ORM 的标准 API
- Hibernate 是 JPA 的实现(最常用)
- Spring Data JPA 是在 JPA 之上的便捷封装,提供 Repository 模式
2.2 Repository 继承体系
Repository<T, ID> (标记接口,无任何方法)
└── CrudRepository<T, ID> (基础 CRUD:save/findById/delete/count)
└── PagingAndSortingRepository<T, ID> (分页 + 排序)
└── JpaRepository<T, ID> (JPA 特有:flush/批量操作/Example 查询)
| 接口 | 提供的能力 |
|---|---|
CrudRepository |
save、saveAll、findById、findAll、count、delete、existsById |
PagingAndSortingRepository |
findAll(Sort)、findAll(Pageable) |
JpaRepository |
flush、saveAndFlush、deleteInBatch、findAll(Example) |
三、实体映射
3.1 基础注解
java
@Entity
@Table(name = "t_user")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** 用户名 */
@Column(name = "user_name", nullable = false, length = 50)
private String userName;
/** 邮箱 */
@Column(name = "email", unique = true)
private String email;
/** 是否激活 */
@Column(name = "is_active")
private Boolean active;
/** 创建时间 */
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
/** 更新时间 */
@Column(name = "updated_at")
private LocalDateTime updatedAt;
}
3.2 常用注解说明
| 注解 | 作用 |
|---|---|
@Entity |
标记为 JPA 实体 |
@Table |
指定表名 |
@Id |
主键 |
@GeneratedValue |
主键生成策略(IDENTITY/SEQUENCE/TABLE/AUTO) |
@Column |
列映射(名称、长度、是否可空等) |
@Transient |
不映射到数据库 |
@Enumerated |
枚举映射(ORDINAL/STRING) |
@Temporal |
日期类型映射 |
@Lob |
大字段(BLOB/CLOB) |
3.3 关联关系
java
// 一对多
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<OrderEntity> orders;
// 多对一
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private UserEntity user;
// 多对多
@ManyToMany
@JoinTable(
name = "t_user_role",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<RoleEntity> roles;
// 一对一
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "profile_id")
private UserProfileEntity profile;
四、查询方法派生(Query Derivation)
4.1 命名规则
Spring Data JPA 解析方法名,自动生成查询:
java
public interface UserRepository extends JpaRepository<UserEntity, Long> {
// WHERE user_name = ?
UserEntity findByUserName(String userName);
// WHERE email = ? AND is_active = ?
List<UserEntity> findByEmailAndActive(String email, Boolean active);
// WHERE user_name LIKE ?
List<UserEntity> findByUserNameLike(String pattern);
// WHERE user_name LIKE '%xxx%'
List<UserEntity> findByUserNameContaining(String keyword);
// WHERE age BETWEEN ? AND ?
List<UserEntity> findByAgeBetween(Integer min, Integer max);
// WHERE age > ? ORDER BY created_at DESC
List<UserEntity> findByAgeGreaterThanOrderByCreatedAtDesc(Integer age);
// WHERE is_active = true (取前3条)
List<UserEntity> findTop3ByActiveTrue();
// 统计
Long countByActive(Boolean active);
// 是否存在
Boolean existsByEmail(String email);
// 删除
void deleteByUserName(String userName);
}
4.2 完整关键字对照表
| 关键字 | 方法名示例 | JPQL 等价 |
|---|---|---|
And |
findByNameAndAge |
WHERE name=? AND age=? |
Or |
findByNameOrAge |
WHERE name=? OR age=? |
Is/Equals |
findByName / findByNameIs |
WHERE name=? |
Between |
findByAgeBetween |
WHERE age BETWEEN ? AND ? |
LessThan |
findByAgeLessThan |
WHERE age < ? |
LessThanEqual |
findByAgeLessThanEqual |
WHERE age <= ? |
GreaterThan |
findByAgeGreaterThan |
WHERE age > ? |
GreaterThanEqual |
findByAgeGreaterThanEqual |
WHERE age >= ? |
After |
findByCreatedAtAfter |
WHERE created_at > ? |
Before |
findByCreatedAtBefore |
WHERE created_at < ? |
IsNull |
findByEmailIsNull |
WHERE email IS NULL |
IsNotNull/NotNull |
findByEmailIsNotNull |
WHERE email IS NOT NULL |
Like |
findByNameLike |
WHERE name LIKE ? |
NotLike |
findByNameNotLike |
WHERE name NOT LIKE ? |
StartingWith |
findByNameStartingWith |
WHERE name LIKE 'xxx%' |
EndingWith |
findByNameEndingWith |
WHERE name LIKE '%xxx' |
Containing |
findByNameContaining |
WHERE name LIKE '%xxx%' |
OrderBy |
findByAgeOrderByNameDesc |
... ORDER BY name DESC |
Not |
findByNameNot |
WHERE name <> ? |
In |
findByAgeIn(Collection) |
WHERE age IN (...) |
NotIn |
findByAgeNotIn(Collection) |
WHERE age NOT IN (...) |
True |
findByActiveTrue |
WHERE active = true |
False |
findByActiveFalse |
WHERE active = false |
IgnoreCase |
findByNameIgnoreCase |
WHERE UPPER(name)=UPPER(?) |
Top/First |
findTop10By... |
LIMIT 10 |
Distinct |
findDistinctByName |
SELECT DISTINCT ... |
五、自定义查询
5.1 @Query 注解(JPQL)
java
public interface UserRepository extends JpaRepository<UserEntity, Long> {
// JPQL 查询
@Query("SELECT u FROM UserEntity u WHERE u.email = :email")
UserEntity findByEmailAddress(@Param("email") String email);
// JPQL 带分页
@Query("SELECT u FROM UserEntity u WHERE u.active = true")
Page<UserEntity> findActiveUsers(Pageable pageable);
// 原生 SQL
@Query(value = "SELECT * FROM t_user WHERE user_name = ?1", nativeQuery = true)
UserEntity findByUserNameNative(String userName);
// 更新操作(需要 @Modifying)
@Modifying
@Query("UPDATE UserEntity u SET u.active = :active WHERE u.id = :id")
int updateActiveStatus(@Param("id") Long id, @Param("active") Boolean active);
// 删除操作
@Modifying
@Query("DELETE FROM UserEntity u WHERE u.active = false")
int deleteInactiveUsers();
}
5.2 @Modifying 注意事项
- 必须配合
@Transactional使用 - 默认不会清除持久化上下文,可加
@Modifying(clearAutomatically = true)自动清除 - 返回值为
int(受影响行数)或void
六、分页与排序
6.1 分页查询
java
// Repository 方法
Page<UserEntity> findByActive(Boolean active, Pageable pageable);
// 调用方式
Pageable pageable = PageRequest.of(0, 10, Sort.by("createdAt").descending());
Page<UserEntity> page = userRepository.findByActive(true, pageable);
// Page 对象包含的信息
page.getContent(); // 当前页数据列表
page.getTotalElements(); // 总记录数
page.getTotalPages(); // 总页数
page.getNumber(); // 当前页码(从0开始)
page.getSize(); // 每页大小
page.hasNext(); // 是否有下一页
page.hasPrevious(); // 是否有上一页
6.2 Slice(轻量分页,不查总数)
java
Slice<UserEntity> findByActive(Boolean active, Pageable pageable);
Slice 不执行 COUNT 查询,性能更好,适合"加载更多"场景。
6.3 排序
java
// 单字段排序
Sort sort = Sort.by("createdAt").descending();
// 多字段排序
Sort sort = Sort.by(
Sort.Order.desc("createdAt"),
Sort.Order.asc("userName")
);
List<UserEntity> users = userRepository.findAll(sort);
七、Specification 动态查询
适合复杂的动态条件组合查询,Repository 需继承 JpaSpecificationExecutor:
java
public interface UserRepository extends JpaRepository<UserEntity, Long>,
JpaSpecificationExecutor<UserEntity> {
}
7.1 构建 Specification
java
public class UserSpecification {
/** 根据用户名模糊查询 */
public static Specification<UserEntity> userNameLike(String userName) {
return (root, query, cb) -> {
if (userName == null || userName.isEmpty()) {
return cb.conjunction();
}
return cb.like(root.get("userName"), "%" + userName + "%");
};
}
/** 根据激活状态查询 */
public static Specification<UserEntity> isActive(Boolean active) {
return (root, query, cb) -> {
if (active == null) {
return cb.conjunction();
}
return cb.equal(root.get("active"), active);
};
}
/** 根据创建时间范围查询 */
public static Specification<UserEntity> createdBetween(LocalDateTime start, LocalDateTime end) {
return (root, query, cb) -> cb.between(root.get("createdAt"), start, end);
}
}
7.2 组合使用
java
// 动态组合条件
Specification<UserEntity> spec = Specification
.where(UserSpecification.userNameLike("张"))
.and(UserSpecification.isActive(true))
.and(UserSpecification.createdBetween(startTime, endTime));
Page<UserEntity> result = userRepository.findAll(spec, pageable);
八、审计功能(Auditing)
自动填充创建时间、修改时间、创建人、修改人。
8.1 启用审计
java
@Configuration
@EnableJpaAuditing
public class JpaAuditConfig {
@Bean
public AuditorAware<String> auditorProvider() {
// 从安全上下文获取当前用户
return () -> Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.map(Authentication::getName);
}
}
8.2 审计基类
java
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@CreatedDate
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@CreatedBy
@Column(name = "created_by", updatable = false)
private String createdBy;
@LastModifiedBy
@Column(name = "updated_by")
private String updatedBy;
}
九、投影(Projection)
只查询部分字段,避免加载整个实体。
9.1 接口投影(Interface Projection)
java
// 定义投影接口
public interface UserSummaryVo {
String getUserName();
String getEmail();
}
// Repository 方法
List<UserSummaryVo> findByActive(Boolean active);
9.2 类投影(Class Projection / DTO Projection)
java
public class UserDto {
private String userName;
private String email;
// 构造函数必须与查询字段对应
public UserDto(String userName, String email) {
this.userName = userName;
this.email = email;
}
}
// 使用 JPQL 构造
@Query("SELECT new com.example.dto.UserDto(u.userName, u.email) FROM UserEntity u WHERE u.active = true")
List<UserDto> findActiveUserSummaries();
十、乐观锁与软删除
10.1 乐观锁
java
@Entity
public class ProductEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productName;
private Integer stock;
/** 版本号,用于乐观锁 */
@Version
private Integer version;
}
更新时 Hibernate 自动在 WHERE 条件中加入 version = ?,并发冲突时抛出 OptimisticLockException。
10.2 软删除
java
@Entity
@Where(clause = "is_deleted = 0")
@SQLDelete(sql = "UPDATE t_user SET is_deleted = 1 WHERE id = ?")
public class UserEntity {
@Column(name = "is_deleted")
private Boolean deleted = false;
}
十一、自定义 Repository 实现
当方法名派生和 @Query 都无法满足需求时,可以自定义实现。
java
// 1. 定义自定义接口
public interface UserRepositoryCustom {
List<UserEntity> findByComplexCondition(String keyword, Integer minAge);
}
// 2. 实现类(命名必须为 Repository名 + Impl)
public class UserRepositoryImpl implements UserRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<UserEntity> findByComplexCondition(String keyword, Integer minAge) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<UserEntity> query = cb.createQuery(UserEntity.class);
Root<UserEntity> root = query.from(UserEntity.class);
List<Predicate> predicates = new ArrayList<>();
if (keyword != null) {
predicates.add(cb.like(root.get("userName"), "%" + keyword + "%"));
}
if (minAge != null) {
predicates.add(cb.greaterThanOrEqualTo(root.get("age"), minAge));
}
query.where(predicates.toArray(new Predicate[0]));
return entityManager.createQuery(query).getResultList();
}
}
// 3. 主 Repository 继承自定义接口
public interface UserRepository extends JpaRepository<UserEntity, Long>, UserRepositoryCustom {
}
十二、事务管理
java
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 默认:运行时异常回滚
@Transactional(rollbackFor = Exception.class)
@Override
public UserEntity createUser(UserEntity user) {
return userRepository.save(user);
}
// 只读事务(优化性能,不加写锁)
@Transactional(readOnly = true)
@Override
public UserEntity getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
}
}
事务传播行为:
| 传播类型 | 说明 |
|---|---|
REQUIRED(默认) |
有事务则加入,没有则新建 |
REQUIRES_NEW |
总是新建事务,挂起当前事务 |
NESTED |
嵌套事务(savepoint) |
SUPPORTS |
有事务则加入,没有则非事务执行 |
NOT_SUPPORTED |
非事务执行,挂起当前事务 |
MANDATORY |
必须在事务中调用,否则抛异常 |
NEVER |
必须非事务调用,否则抛异常 |
十三、性能优化
13.1 N+1 问题解决
java
// 使用 @EntityGraph 一次性加载关联
@EntityGraph(attributePaths = {"orders", "roles"})
List<UserEntity> findByActive(Boolean active);
// 或在 JPQL 中使用 JOIN FETCH
@Query("SELECT u FROM UserEntity u LEFT JOIN FETCH u.orders WHERE u.id = :id")
UserEntity findWithOrdersById(@Param("id") Long id);
13.2 批量操作
java
// 批量插入(需配置 hibernate.jdbc.batch_size)
@Transactional(rollbackFor = Exception.class)
public void batchSave(List<UserEntity> users) {
int batchSize = 500;
for (int i = 0; i < users.size(); i++) {
userRepository.save(users.get(i));
if (i % batchSize == 0) {
entityManager.flush();
entityManager.clear();
}
}
}
13.3 配置建议
yaml
spring:
jpa:
properties:
hibernate:
jdbc:
batch_size: 500
order_inserts: true
order_updates: true
default_batch_fetch_size: 100
open-in-view: false # 生产环境建议关闭
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
十四、完整示例代码
以下是一个完整的用户管理模块示例:
14.1 实体类
java
package com.example.entity;
import javax.persistence.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
/**
* 用户实体
*/
@Entity
@Table(name = "t_user")
@EntityListeners(AuditingEntityListener.class)
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** 用户名 */
@Column(name = "user_name", nullable = false, length = 50)
private String userName;
/** 邮箱 */
@Column(name = "email", unique = true, length = 100)
private String email;
/** 年龄 */
@Column(name = "age")
private Integer age;
/** 是否激活 */
@Column(name = "is_active")
private Boolean active = true;
/** 创建时间 */
@CreatedDate
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
/** 更新时间 */
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// getter / setter 省略
}
14.2 Repository
java
package com.example.dao;
import com.example.entity.UserEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
/**
* 用户数据访问层
*/
public interface UserRepository extends JpaRepository<UserEntity, Long>,
JpaSpecificationExecutor<UserEntity> {
/** 根据用户名查询 */
Optional<UserEntity> findByUserName(String userName);
/** 根据邮箱查询 */
Optional<UserEntity> findByEmail(String email);
/** 根据激活状态分页查询 */
Page<UserEntity> findByActive(Boolean active, Pageable pageable);
/** 根据用户名模糊查询 */
List<UserEntity> findByUserNameContaining(String keyword);
/** 根据年龄范围查询 */
List<UserEntity> findByAgeBetween(Integer minAge, Integer maxAge);
/** 是否存在该邮箱 */
Boolean existsByEmail(String email);
/** 自定义更新 */
@Modifying
@Query("UPDATE UserEntity u SET u.email = :email WHERE u.id = :id")
int updateEmail(@Param("id") Long id, @Param("email") String email);
/** 自定义统计 */
@Query("SELECT COUNT(u) FROM UserEntity u WHERE u.active = :active")
Long countByActiveStatus(@Param("active") Boolean active);
}
14.3 Service 接口
java
package com.example.service;
import com.example.entity.UserEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
/**
* 用户服务接口
*/
public interface UserService {
/** 创建用户 */
UserEntity createUser(UserEntity user);
/** 根据ID查询用户 */
UserEntity getUserById(Long id);
/** 分页查询激活用户 */
Page<UserEntity> listActiveUsers(Pageable pageable);
/** 更新邮箱 */
void updateEmail(Long id, String email);
/** 删除用户 */
void deleteUser(Long id);
}
14.4 Service 实现
java
package com.example.service.impl;
import com.example.dao.UserRepository;
import com.example.entity.UserEntity;
import com.example.service.UserService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 用户服务实现
*/
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional(rollbackFor = Exception.class)
@Override
public UserEntity createUser(UserEntity user) {
// 检查邮箱是否已存在
if (userRepository.existsByEmail(user.getEmail())) {
throw new RuntimeException("邮箱已被注册");
}
return userRepository.save(user);
}
@Transactional(readOnly = true)
@Override
public UserEntity getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在,ID: " + id));
}
@Transactional(readOnly = true)
@Override
public Page<UserEntity> listActiveUsers(Pageable pageable) {
return userRepository.findByActive(true, pageable);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void updateEmail(Long id, String email) {
// 检查用户是否存在
if (!userRepository.existsById(id)) {
throw new RuntimeException("用户不存在,ID: " + id);
}
userRepository.updateEmail(id, email);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
14.5 Controller
java
package com.example.controller;
import com.example.entity.UserEntity;
import com.example.service.UserService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.*;
/**
* 用户管理控制器
*/
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
/** 创建用户 */
@PostMapping
public UserEntity createUser(@RequestBody UserEntity user) {
return userService.createUser(user);
}
/** 根据ID查询 */
@GetMapping("/{id}")
public UserEntity getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
/** 分页查询激活用户 */
@GetMapping
public Page<UserEntity> listActiveUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
PageRequest pageRequest = PageRequest.of(page, size, Sort.by("createdAt").descending());
return userService.listActiveUsers(pageRequest);
}
/** 更新邮箱 */
@PutMapping("/{id}/email")
public void updateEmail(@PathVariable Long id, @RequestParam String email) {
userService.updateEmail(id, email);
}
/** 删除用户 */
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
}
14.6 配置文件
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQL8Dialect
jdbc:
batch_size: 500
open-in-view: false
14.7 Maven 依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
十五、Spring Data JPA vs MyBatis 选型建议
| 维度 | Spring Data JPA | MyBatis |
|---|---|---|
| 开发效率 | 高(单表 CRUD 零 SQL) | 中(需写 XML/注解 SQL) |
| 复杂查询 | Specification / JPQL(较繁琐) | 原生 SQL + 动态 SQL(灵活) |
| 学习曲线 | 较陡(需理解 JPA 生命周期) | 较平(SQL 即所得) |
| 性能调优 | 需理解懒加载、缓存、N+1 | 直接优化 SQL |
| 数据库无关性 | 好(JPQL 抽象) | 差(SQL 方言绑定) |
| 适用场景 | 领域模型丰富、单 |