Spring Data JPA 最神奇的特性:只需定义接口,无需实现类,就能获得完整的 CRUD 和查询功能 。这背后是动态代理 + 查询方法解析 + 元模型三大技术加持
一、核心原理:动态代理魔法
1. 魔法接口示例
java
// 定义接口(无需实现类)
public interface UserRepository extends JpaRepository<User, Long> {
// 自定义查询方法(无需实现)
List<User> findByUsernameAndAge(String username, Integer age);
}
// 直接注入使用
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // 接口,但 Spring 会注入代理对象
public void doWork() {
userRepository.findById(1L); // 内置方法
userRepository.findByUsernameAndAge("tom", 18); // 自动实现的方法
}
}
2. 动态代理创建流程
java
// Spring 容器启动时
1. 扫描 JpaRepository 接口
↓
2. 创建 RepositoryFactorySupport
↓
3. 生成 JDK 动态代理(Proxy)
↓
4. 代理转发给 SimpleJpaRepository 或 QueryExecutorMethodInterceptor
↓
5. 代理对象注入到业务类
关键源码:
java
// RepositoryFactorySupport.getRepository()
public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {
// 创建代理
ProxyFactory result = new ProxyFactory();
result.setTarget(target); // 目标实现
result.setInterfaces(repositoryInterface); // 代理接口
// 添加拦截器:处理自定义查询方法
result.addAdvice(QueryExecutorMethodInterceptor);
return (T) result.getProxy(); // 返回代理对象
}
二、查询方法命名解析策略
1. 方法名解析规则
Spring Data JPA 将方法名按关键字拆分,转换为 JPQL/SQL:
sql
// 方法名:findByUsernameAndAgeOrderByCreateTimeDesc
// 解析步骤:
find → SELECT
By → WHERE
Username → username = ?1
And → AND
Age → age = ?2
OrderBy → ORDER BY
CreateTime→ createTime
Desc → DESC
完整转换:
sql
-- 生成的 JPQL
SELECT u FROM User u
WHERE u.username = ?1 AND u.age = ?2
ORDER BY u.createTime DESC
2. 支持的关键字
| 关键字 | 示例方法名 | 生成 JPQL 片段 | 说明 |
|---|---|---|---|
| And | findByNameAndAge |
where name = ?1 and age = ?2 |
逻辑与 |
| Or | findByNameOrAge |
where name = ?1 or age = ?2 |
逻辑或 |
| Between | findByAgeBetween |
where age between ?1 and ?2 |
范围查询 |
| LessThan | findByAgeLessThan |
where age < ?1 |
小于 |
| GreaterThan | findByAgeGreaterThan |
where age > ?1 |
大于 |
| Like | findByNameLike |
where name like ?1 |
模糊匹配 |
| NotLike | findByNameNotLike |
where name not like ?1 |
非模糊 |
| IsNull | findByNameIsNull |
where name is null |
空值 |
| IsNotNull | findByNameIsNotNull |
where name is not null |
非空 |
| In | findByIdIn |
where id in (?1) |
包含 |
| OrderBy | findByNameOrderByAgeDesc |
where name = ?1 order by age desc |
排序 |
| Count | countByName |
select count(*) where name = ?1 |
计数 |
| Delete | deleteByName |
delete where name = ?1 |
删除 |
| Exists | existsByName |
select case when count(*)>0 then true else false end where name = ?1 |
存在判断 |
3. 复杂查询示例
java
// 示例实体
@Entity
public class User {
private String username;
private Integer age;
private String email;
private Boolean active;
private LocalDateTime createTime;
}
// Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 多条件+排序
List<User> findByUsernameAndAgeGreaterThanOrderByCreateTimeDesc(
String username, Integer minAge);
// 模糊+范围+计数
Long countByUsernameLikeAndAgeBetween(String pattern, Integer min, Integer max);
// In 查询+非空
List<User> findByEmailInAndActiveIsTrue(List<String> emails);
// 存在判断
Boolean existsByUsername(String username);
}
三、@Query 注解:自定义 JPQL/SQL
当方法命名无法满足时,使用 @Query:
1. JPQL 查询
java
public interface UserRepository extends JpaRepository<User, Long> {
// JPQL(面向对象,操作实体类)
@Query("SELECT u FROM User u WHERE u.username = :name AND u.age > :minAge")
List<User> findCustomUsers(@Param("name") String username,
@Param("minAge") Integer age);
// 使用 @Modifying 进行更新/删除
@Modifying
@Query("UPDATE User u SET u.active = false WHERE u.lastLogin < :date")
int deactivateInactiveUsers(@Param("date") LocalDateTime date);
}
2. 原生 SQL 查询
java
@Query(value = "SELECT * FROM users WHERE email LIKE %:domain",
nativeQuery = true)
List<User> findByEmailDomain(@Param("domain") String domain);
@Query(value = "SELECT u.id, u.username FROM users u WHERE u.age > ?1",
nativeQuery = true)
List<Object[]> findIdAndUsernameByAge(Integer age);
四、分页与排序
1. 内置分页支持
java
// 返回 Page<T> 或 Slice<T>
Page<User> findByUsername(String username, Pageable pageable);
// 使用
Pageable pageable = PageRequest.of(0, 10, Sort.by("age").descending());
Page<User> page = userRepository.findByUsername("tom", pageable);
// 获取结果
List<User> content = page.getContent(); // 当前页数据
long total = page.getTotalElements(); // 总记录数
int totalPages = page.getTotalPages(); // 总页数
boolean hasNext = page.hasNext(); // 是否有下一页
生成的 SQL:
sql
-- 查询数据
SELECT * FROM users WHERE username = ? ORDER BY age DESC LIMIT 0, 10
-- 查询总数(自动执行)
SELECT COUNT(*) FROM users WHERE username = ?
2. Slice 与 Page 区别
java
// Slice:不查询总数,性能更高
Slice<User> findByAge(Integer age, Pageable pageable);
// 只查询数据 + 是否有下一页(不执行 COUNT)
五、自定义实现:复杂的业务逻辑
1. 自定义 Repository 接口
java
// 1. 定义接口
public interface UserRepositoryCustom {
List<User> complexSearch(String username, Integer minAge, Boolean active);
}
// 2. 实现接口(命名必须后缀 Impl)
@Repository
public class UserRepositoryImpl implements UserRepositoryCustom {
@PersistenceContext
private EntityManager em; // 注入 JPA 实体管理器
@Override
public List<User> complexSearch(String username, Integer minAge, Boolean active) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
// 动态构建查询
List<Predicate> predicates = new ArrayList<>();
if (username != null) {
predicates.add(cb.like(root.get("username"), "%" + username + "%"));
}
if (minAge != null) {
predicates.add(cb.greaterThanOrEqualTo(root.get("age"), minAge));
}
if (active != null) {
predicates.add(cb.equal(root.get("active"), active));
}
query.where(predicates.toArray(new Predicate[0]));
return em.createQuery(query).getResultList();
}
}
// 3. 主 Repository 继承自定义接口
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
// 自动获得标准 CRUD + 自定义方法
}
2. 使用 JPA Specification
java
// 更优雅的动态查询
public interface UserRepository extends JpaRepository<User, Long>,
JpaSpecificationExecutor<User> {
}
// 定义 Specification
public static Specification<User> hasUsername(String username) {
return (root, query, cb) ->
username == null ? null : cb.equal(root.get("username"), username);
}
// 使用
List<User> users = userRepository.findAll(
Specification.where(hasUsername("tom"))
.and(hasMinAge(18))
);
六、性能优化与陷阱
1. N+1 查询问题
java
// 危险:触发 N+1
@Entity
public class Order {
@OneToMany
private List<OrderItem> items; // 懒加载
}
// 查询
List<Order> orders = orderRepository.findAll(); // 1 条 SQL
for (Order order : orders) {
order.getItems().size(); // 触发 N 条 SQL
}
// 优化:使用 @EntityGraph
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
@EntityGraph(attributePaths = "items")
List<Order> findByUserId(Long userId); // 一条 JOIN 查询
}
// 或 @Query JOIN FETCH
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.userId = :userId")
List<Order> findWithItems(@Param("userId") Long userId);
2. 批量操作优化
java
// 批量插入
List<User> users = IntStream.range(0, 10000)
.mapToObj(i -> new User("user" + i, 18))
.collect(Collectors.toList());
// ❌ 低效:默认每条 INSERT 都发一次 SQL
userRepository.saveAll(users);
// ✅ 高效:配置批量插入
// application.yml
spring:
jpa:
properties:
hibernate:
jdbc:
batch_size: 500 # 每 500 条一次 SQL
order_inserts: true
order_updates: true
// 此时会生成:INSERT INTO users (...) VALUES (...), (...), ...
3. 只读查询优化
java
// 只读事务提升性能
@Transactional(readOnly = true)
public List<User> searchUsers() {
return userRepository.findAll(); // 不获取 Flush 锁,性能更好
}
七、对比:JPA vs MyBatis
| 特性 | Spring Data JPA | MyBatis |
|---|---|---|
| SQL 生成 | 自动生成 JPQL/SQL | 手动编写 XML/SQL |
| 开发效率 | 极高(无需写 SQL) | 较低(需写 SQL) |
| 灵活性 | 中等(复杂查询需 @Query) | 极高(任意 SQL) |
| 学习曲线 | 陡峭(理解 JPA、缓存) | 平缓(简单) |
| 性能 | 中等(有 ORM 开销) | 高(直接 SQL) |
| 适用场景 | CRUD 为主、对象驱动 | 复杂报表、遗留系统 |
八、实践总结
1.优先使用方法命名查询:简单、清晰、无需 SQL
2.复杂查询用 @Query:保持 Repository 接口简洁
3.避免 N+1:使用 @EntityGraph 或 JOIN FETCH
4.批量操作配置 batch_size:性能提升 10 倍
5.只读查询加 @Transactional(readOnly = true)
6.自定义实现后缀必须是 Impl:否则 Spring 找不到
7.动态查询用 Specification:类型安全、可复用
总结 :JPA 是面向对象的查询语言,不是 SQL 的替代品。理解 entity lifecycle 和 flush 机制是精通 JPA 的关键。