Repository 方法解析
Spring Data JPA 是 Spring 生态中用于简化数据访问层的核心框架。它通过 Repository 抽象,极大地减少了 DAO 层的样板代码。本文将深入分析 Repository 的接口层次结构、核心实现类的源码、方法名解析机制以及高级特性。
一、Repository 接口层次
Spring Data JPA 定义了一套清晰的接口继承体系,从最基础的标记接口到功能丰富的 JPA 专属接口,层层递进。
1.1 接口继承结构
Repository (标记接口)
└─ CrudRepository (增删改查)
└─ PagingAndSortingRepository (分页排序)
└─ JpaRepository (JPA 特性)
└─ JpaSpecificationExecutor (Specification 查询)Repository
Repository 是一个空的标记接口,它的唯一作用是标识一个类为 Spring Data Repository。Spring 容器会扫描继承了该接口的子接口并为其生成代理实现。
public interface Repository<T, ID> {
}CrudRepository
CrudRepository 定义了最基础的 CRUD 操作方法:
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
Optional<T> findById(ID id);
boolean existsById(ID id);
Iterable<T> findAll();
long count();
void deleteById(ID id);
void delete(T entity);
void deleteAll();
}PagingAndSortingRepository
在 CRUD 基础上增加了分页和排序能力:
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}JpaRepository
JpaRepository 继承了 PagingAndSortingRepository,并加入了 JPA 特有的批量操作和刷新方法:
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
void flush();
<S extends T> S saveAndFlush(S entity);
<S extends T> List<S> saveAllAndFlush(Iterable<S> entities);
void deleteAllInBatch();
T getReferenceById(ID id);
<S extends T> List<S> findAll(Example<S> example);
}JpaSpecificationExecutor
该接口提供了基于 Specification 的动态查询能力:
@NoRepositoryBean
public interface JpaSpecificationExecutor<T> {
Optional<T> findOne(Specification<T> spec);
List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable);
long count(Specification<T> spec);
boolean exists(Specification<T> spec);
}1.2 自定义 Repository 接口
通常的做法是自定义接口继承 JpaRepository 和 JpaSpecificationExecutor:
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
List<User> findByLastName(String lastName);
Page<User> findByAgeGreaterThan(int age, Pageable pageable);
}二、SimpleJpaRepository 源码分析
SimpleJpaRepository 是 JpaRepository 接口的默认实现,位于 org.springframework.data.jpa.repository.support 包中。
2.1 类定义与构造
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
private final JpaEntityInformation<T, ID> entityInformation;
private final EntityManager em;
private final PersistenceProvider provider;
public SimpleJpaRepository(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
this.entityInformation = entityInformation;
this.em = entityManager;
this.provider = PersistenceProvider.fromEntityManager(entityManager);
}
}关键点:
@Transactional(readOnly = true):类级别只读事务,save/delete等方法通过方法级别@Transactional覆盖。JpaEntityInformation:封装实体元数据(表名、ID 属性等)。EntityManager:所有数据库操作最终委托给 JPA 的EntityManager。
2.2 核心方法实现
save 方法
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}- 新实体(ID 为
null)调用persist插入;已有实体调用merge更新。 isNew默认检查@Id字段是否为null。
findById 方法
@Override
public Optional<T> findById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
Class<T> domainType = getDomainClass();
if (metadata == null) {
return Optional.ofNullable(em.find(domainType, id));
}
LockModeType type = metadata.getLockModeType();
return Optional.ofNullable(type == null ? em.find(domainType, id) : em.find(domainType, id, type));
}findAll 分页版本
@Override
public Page<T> findAll(Pageable pageable) {
if (pageable.isUnpaged()) return new PageImpl<>(findAll());
return findAll(new Specification<T>() {
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return null;
}
}, pageable);
}delete 方法
@Transactional
@Override
public void delete(T entity) {
Assert.notNull(entity, "Entity must not be null!");
if (entityInformation.isNew(entity)) return;
em.remove(em.contains(entity) ? entity : em.merge(entity));
}2.3 查询执行机制
findAll(Specification, Pageable) 是核心实现:
@Override
public Page<T> findAll(Specification<T> spec, Pageable pageable) {
CriteriaQuery<T> query = getQuery(spec, pageable.getSort());
return readPage(query, getDomainClass(), pageable, spec);
}
protected <S extends T> Page<S> readPage(CriteriaQuery<S> query, Class<S> domainClass,
Pageable pageable, Specification<S> spec) {
Query<S> typedQuery = em.createQuery(query);
typedQuery.setFirstResult((int) pageable.getOffset());
typedQuery.setMaxResults(pageable.getPageSize());
List<S> content = typedQuery.getResultList();
Long total = executeCountQuery(getCountQuery(spec, domainClass));
return new PageImpl<>(content, pageable, total);
}三、QueryLookupStrategy 方法名解析
QueryLookupStrategy 决定如何为 Repository 方法生成查询实现。
3.1 QueryLookupStrategy 策略
位于 org.springframework.data.repository.query 包中:
public interface QueryLookupStrategy {
RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
ProjectionFactory factory, NamedQueries namedQueries);
enum Key {
CREATE, // 仅通过方法名解析
USE_DECLARED_QUERY, // 仅使用 @Query 声明式查询
CREATE_IF_NOT_FOUND; // 默认:先找 @Query,没有则从方法名派生
}
}三种策略模式:
CREATE:只使用方法名派生查询。USE_DECLARED_QUERY:只使用@Query等声明式查询。CREATE_IF_NOT_FOUND(默认):优先查找@Query,找不到则通过方法名派生。
3.2 策略的构建过程
JpaQueryLookupStrategy 的 resolveQuery 实现了解析逻辑:
public class JpaQueryLookupStrategy implements QueryLookupStrategy {
private final Key strategy;
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,
ProjectionFactory factory, NamedQueries namedQueries) {
JpaQueryMethod queryMethod = this.factory.build(method, metadata, factory);
String queryName = queryMethod.getNamedQueryName();
switch (strategy) {
case CREATE:
return new PartTreeJpaQuery(queryMethod, em);
case USE_DECLARED_QUERY:
// 查找 @Query 或 NamedQuery
case CREATE_IF_NOT_FOUND:
// 先查找 @Query,如果没有则 fallback 到方法名解析
}
}
}3.3 配置方式
通过 @EnableJpaRepositories 的 queryLookupStrategy 指定策略:
@EnableJpaRepositories(
basePackages = "com.example.repository",
queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND
)
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}四、PartTreeJpaQuery 方法名解析
当策略选定为方法名派生查询时,PartTreeJpaQuery 负责将方法名解析为 JPQL 语句。
4.1 PartTree 解析引擎
PartTreeJpaQuery 内部使用 PartTree 类解析方法名,PartTree 位于 org.springframework.data.repository.query.parser 包中。
public class PartTreeJpaQuery extends AbstractJpaQuery {
private final PartTree tree;
private final JpaParameters parameters;
public PartTreeJpaQuery(JpaQueryMethod method, EntityManager em) {
super(method, em);
this.tree = new PartTree(method.getName(), method.getEntityInformation().getJavaType());
this.tree.verifyParameters(method.getParameters());
}
}4.2 方法名解析规则
前缀(Subject)解析
PartTree 支持以下查询前缀:
| 前缀 | 语义 | 示例 |
|---|---|---|
find...By | 查询 | findByLastName |
read...By | 查询 | readByEmail |
query...By | 查询 | queryByStatus |
get...By | 查询 | getByUsername |
count...By | 计数 | countByAge |
exists...By | 判断存在 | existsByEmail |
delete...By | 删除 | deleteByStatus |
remove...By | 删除 | removeByExpired |
条件(Predicate)解析
条件部分由 OrParts 和 Part 组成。每个 Part 包含属性和操作符:
public class Part {
private final PropertyPath propertyPath;
private final Type type;
public enum Type {
BETWEEN, IS_NOT_NULL, IS_NULL, GREATER_THAN, LESS_THAN,
GREATER_THAN_EQUAL, LESS_THAN_EQUAL, NOT_LIKE, LIKE,
STARTING_WITH, ENDING_WITH, NOT_CONTAINING, CONTAINING,
AFTER, BEFORE, TRUE, FALSE, IN, NOT_IN, NOT_EQUAL,
SIMPLE_PROPERTY, NEGATING_SIMPLE_PROPERTY
}
}方法名关键字的映射关系:
| 方法名关键字 | JPQL 片段 | Part.Type |
|---|---|---|
And | AND | — |
Or | OR | — |
IgnoreCase | LOWER(...) | — |
Between | BETWEEN ?1 AND ?2 | BETWEEN |
LessThan | < ?1 | LESS_THAN |
GreaterThan | > ?1 | GREATER_THAN |
IsNull | IS NULL | IS_NULL |
IsNotNull / NotNull | IS NOT NULL | IS_NOT_NULL |
Like | LIKE ?1 | LIKE |
StartingWith | LIKE ?1(拼接 %) | STARTING_WITH |
EndingWith | LIKE ?1(拼接 %) | ENDING_WITH |
Containing | LIKE ?1(拼接 %) | CONTAINING |
In | IN ?1 | IN |
NotIn | NOT IN ?1 | NOT_IN |
True / False | = TRUE / = FALSE | TRUE / FALSE |
OrderBy | ORDER BY | — |
4.3 JPQL 生成示例
以 findByLastNameAndAgeGreaterThanOrderByAgeDesc 为例,生成 SELECT u FROM User u WHERE u.lastName = ?1 AND u.age > ?2 ORDER BY u.age DESC:
@Override
public Query doCreateQuery(Object[] values) {
String jpql = String.format("SELECT u FROM %s u WHERE %s %s",
getEntityName(), whereClause, orderByClause);
Query query = getEntityManager().createQuery(jpql);
new ParameterBinder(getQueryMethod().getParameters()).bind(query, values);
return query;
}4.4 参数绑定
ParameterBinder 负责将方法参数与 JPQL 占位符对应:
public void bind(Query query, Object[] values) {
int methodParamIndex = 0;
for (PartTree.OrPart orPart : tree) {
for (Part part : orPart) {
if (part.shouldBind()) {
Object value = values[methodParamIndex++];
String paramName = part.getProperty().getName();
if (part.shouldIgnoreCase()) {
query.setParameter(paramName, value.toString().toLowerCase());
} else {
query.setParameter(paramName, value);
}
}
}
}
}五、@Query 注解自定义查询
当方法名派生无法满足复杂查询时,可以使用 @Query 注解直接编写 JPQL 或原生 SQL。
5.1 @Query 注解定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Query {
String value() default "";
String countQuery() default "";
String countProjection() default "";
boolean nativeQuery() default false;
String name() default "";
QueryHints[] hints() default {};
}5.2 JPQL 查询
public interface UserRepository extends JpaRepository<User, Long> {
// JPQL 位置参数
@Query("SELECT u FROM User u WHERE u.email = ?1 AND u.status = ?2")
Optional<User> findByEmailAndStatus(String email, UserStatus status);
// JPQL 命名参数(推荐)
@Query("SELECT u FROM User u WHERE u.email = :email AND u.status = :status")
Optional<User> findByEmailWithNamedParam(@Param("email") String email, @Param("status") UserStatus status);
// JOIN FETCH 避免 N+1
@Query("SELECT u FROM User u LEFT JOIN FETCH u.roles WHERE u.departmentId = :deptId")
List<User> findByDepartmentWithRoles(@Param("deptId") Long deptId);
}5.3 原生 SQL 查询
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT * FROM users u WHERE u.created_at >= ?1", nativeQuery = true)
List<User> findUsersCreatedAfter(Date date);
// 原生 SQL + 分页(必须提供 countQuery)
@Query(
value = "SELECT * FROM users WHERE status = :status ORDER BY created_at DESC",
countQuery = "SELECT COUNT(*) FROM users WHERE status = :status",
nativeQuery = true
)
Page<User> findUsersByStatus(@Param("status") String status, Pageable pageable);
}注意:原生 SQL 返回字段必须与实体映射一致,否则会抛出异常。
5.4 SpEL 表达式支持
@Query 支持在 JPQL 中使用 SpEL:
public interface UserRepository extends JpaRepository<User, Long> {
// #{#entityName} 会被替换为实体的名称
@Query("SELECT u FROM #{#entityName} u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
}这在泛型基接口中特别有用。例如用 #{#entityName} 定义通用软删除过滤。
5.5 锁和查询提示
public interface UserRepository extends JpaRepository<User, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT u FROM User u WHERE u.id = :id")
Optional<User> findByIdWithLock(@Param("id") Long id);
@QueryHints(@QueryHint(name = "org.hibernate.cacheable", value = "true"))
@Query("SELECT u FROM User u WHERE u.status = :status")
List<User> findByStatusWithHints(@Param("status") UserStatus status);
}六、@Modifying 更新操作
默认 @Query 查询都是只读 SELECT 操作。执行 INSERT、UPDATE、DELETE 必须使用 @Modifying 注解。
6.1 @Modifying 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Modifying {
boolean flushAutomatically() default false; // 执行前是否自动 flush
boolean clearAutomatically() default false; // 执行后是否自动清除 Persistence Context
}6.2 基本用法
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.lastLoginAt < :date")
int deactivateInactiveUsers(@Param("date") Date date, @Param("status") UserStatus status);
@Modifying
@Query("DELETE FROM User u WHERE u.status = :status AND u.createdAt < :date")
int deleteOldUsers(@Param("status") UserStatus status, @Param("date") Date date);
}6.3 Persistence Context 管理
使用 @Modifying 时需要特别注意 Persistence Context 的状态一致性:
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE User u SET u.email = :newEmail WHERE u.id = :id")
int updateEmail(@Param("id") Long id, @Param("newEmail") String newEmail);
}flushAutomatically = true:执行更新前自动将 Persistence Context 中未刷新的变更同步到数据库。clearAutomatically = true:更新执行后自动清除 Persistence Context,避免缓存过期数据。
6.4 @Modifying 的限制
- 不支持动态排序:
@Modifying查询不能使用Sort或Pageable参数。 - 不支持级联:JPQL 的更新/删除操作不会级联到关联实体。
- 不触发 JPA 生命周期回调:
@PreUpdate、@PreRemove等回调不会在批量操作中触发。
七、投影(Projections)
Spring Data JPA 支持多种投影方式,允许查询返回部分字段而非整个实体,以提升查询性能。
7.1 接口投影
封闭投影(Closed Projection)
接口方法与实体属性一一对应,Spring Data JPA 优化 JPQL 仅查询涉及字段:
public interface UserSummary {
Long getId();
String getUsername();
String getEmail();
}
public interface UserRepository extends JpaRepository<User, Long> {
// 自动生成 SELECT u.id, u.username, u.email FROM User u
List<UserSummary> findByDepartment(String department);
@Query("SELECT u.id AS id, u.username AS username, u.email AS email FROM User u WHERE u.status = :status")
List<UserSummary> findSummariesByStatus(@Param("status") UserStatus status);
}开放投影(Open Projection)
开放投影通过 @Value 注解组合多个字段:
public interface UserDetail {
Long getId();
String getUsername();
@Value("#{target.firstName + ' ' + target.lastName}")
String getFullName();
@Value("#{@userFormatter.format(target)}")
String getFormattedUser();
}注意:开放投影无法优化
SELECT子句,会查询所有字段再计算 SpEL。
7.2 类投影(DTO 投影)
使用 DTO 类作为投影,要求构造器参数名与实体属性名匹配:
public class UserDTO {
private final Long id;
private final String username;
private final String email;
public UserDTO(Long id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
// getters...
}
public interface UserRepository extends JpaRepository<User, Long> {
List<UserDTO> findByStatus(UserStatus status);
// 必须使用 new 语法
@Query("SELECT new com.example.dto.UserDTO(u.id, u.username, u.email) FROM User u WHERE u.department = :dept")
List<UserDTO> findDTOByDepartment(@Param("dept") String department);
}7.3 动态投影
动态投影允许在调用方法时动态指定返回的投影类型:
public interface UserRepository extends JpaRepository<User, Long> {
<T> List<T> findByStatus(UserStatus status, Class<T> projectionType);
<T> T findProjectedById(Long id, Class<T> projectionType);
}
@Service
public class UserService {
public void demo() {
List<UserSummary> summaries = userRepository.findByStatus(UserStatus.ACTIVE, UserSummary.class);
List<UserDTO> dtoList = userRepository.findByStatus(UserStatus.ACTIVE, UserDTO.class);
List<User> users = userRepository.findByStatus(UserStatus.ACTIVE, User.class);
}
}7.4 嵌套投影
接口投影支持嵌套关联实体的投影:
public interface UserWithRole {
Long getId();
String getUsername();
List<RoleSummary> getRoles();
interface RoleSummary {
Long getId();
String getName();
}
}7.5 投影最佳实践
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 只读展示 | 接口投影 | 零侵入,自动优化 SQL |
| 数据传输层(DTO) | 类投影 | 序列化兼容性好 |
| 需要组合逻辑 | 开放投影 | @Value 支持 SpEL |
| 多场景复用 | 动态投影 | 灵活切换投影类型 |
| 需要关联字段 | 嵌套接口投影 | 自动处理 JOIN |
八、分页排序
8.1 Sort 排序
Sort 支持多字段排序和空值处理:
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByStatus(UserStatus status, Sort sort);
}
Sort sort = Sort.by("lastName").ascending()
.and(Sort.by("createdAt").descending());
Sort sortWithNulls = Sort.by(
Sort.Order.asc("lastName").nullsFirst(),
Sort.Order.desc("createdAt").nullsLast()
);
@Query("SELECT u FROM User u WHERE u.status = :status")
List<User> findByStatusWithSort(@Param("status") UserStatus status, Sort sort);8.2 Pageable 分页
Pageable 的标准实现为 PageRequest:
Pageable pageable = PageRequest.of(0, 20, Sort.by("lastName").ascending());
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findByDepartment(String department, Pageable pageable);
@Query("SELECT u FROM User u WHERE u.age >= :minAge")
Page<User> findAdults(@Param("minAge") int minAge, Pageable pageable);
@Query(value = "SELECT * FROM users WHERE status = :status",
countQuery = "SELECT COUNT(*) FROM users WHERE status = :status",
nativeQuery = true)
Page<User> findByStatusNative(@Param("status") String status, Pageable pageable);
}8.3 Page 接口
public interface Page<T> extends Slice<T> {
int getTotalPages();
long getTotalElements();
int getNumber();
int getSize();
int getNumberOfElements();
List<T> getContent();
boolean hasContent();
boolean isFirst();
boolean isLast();
boolean hasNext();
boolean hasPrevious();
Sort getSort();
Pageable getPageable();
}8.4 Slice 与 Page
Slice 不执行总数查询,性能更好:
| 特性 | Slice | Page |
|---|---|---|
| 总记录数 | 不支持 | 支持 |
| 额外查询 | 无 | 执行 COUNT |
| 适用场景 | 无限滚动 | 传统分页 |
Slice<User> findByStatus(UserStatus status, Pageable pageable); // 不执行 count
Page<User> findByStatus(UserStatus status, Pageable pageable); // 执行 count8.5 Web 参数传递
Spring Data 自动解析 page、size、sort 请求参数:
@RestController
public class UserController {
@GetMapping("/users")
public Page<UserDTO> listUsers(
@PageableDefault(page = 0, size = 20, sort = "lastName", direction = Sort.Direction.ASC)
Pageable pageable
) { return userService.getUsers(pageable); }
}请求示例:GET /users?page=0&size=10&sort=lastName,asc
8.6 数据库分页 SQL
-- MySQL / PostgreSQL: LIMIT ? OFFSET ?
SELECT u.* FROM users u ORDER BY u.last_name ASC LIMIT ? OFFSET ?;
-- Oracle / SQL Server: OFFSET ? ROWS FETCH NEXT ? ROWS ONLY
SELECT u.* FROM users u ORDER BY u.last_name ASC OFFSET ? ROWS FETCH NEXT ? ROWS ONLY;九、源码执行链路
// 代理创建
@EnableJpaRepositories → JpaRepositoryFactoryBean → SimpleJpaRepository 实例化
// 方法名派生查询调用
findByLastName("Smith")
→ QueryExecutorMethodInterceptor.doInvoke()
→ RepositoryQuery.execute()
→ PartTreeJpaQuery.doCreateQuery()
→ PartTree.build() // 方法名解析
→ ParameterBinder.bind() // 参数绑定
→ EntityManager.createQuery() → Query.getResultList()
// @Query 注解调用
findByEmail("test@example.com")
→ SimpleJpaQuery.execute()
→ EntityManager.createQuery(jpql) → Query.setParameter() → Query.getResultList()十、最佳实践
10.1 设计建议
优先方法名派生:简单条件查询时最简洁易读。
复杂查询用
@Query:多表关联、聚合函数时显式 JPQL 更清晰。善用投影优化:只需部分字段时显著减少数据传输量。
合理选择分页:大数据量优先使用
Slice而非Page。性能优化:批量插入用
saveAllAndFlush;只读查询用投影;关联查询用JOIN FETCH避免N+1;批量更新用@Modifying。
10.3 常见陷阱
N+1查询:延迟加载循环访问产生,使用JOIN FETCH或@EntityGraph解决。@Modifying缓存不一致:设置clearAutomatically = true避免过期数据。- 方法名过长:超过 4 个条件改用
@Query或Specification。 - 原生 SQL 兼容性:降低可移植性,仅无法用 JPQL 表达时使用。