JPA 查询方法
方法名查询(
findByXxx)覆盖了大部分 CRUD,但复杂查询要靠@Query写 JPQL 或原生 SQL。对应 Eloquent 的DB::select/ 查询构建器。
@Query 写 JPQL:面向实体
JPQL 是 JPA 自己的查询语言,面向实体(不是表):
java
public interface PostRepository extends JpaRepository<Post, Long> {
@Query("SELECT p FROM Post p WHERE p.status = :status AND p.title LIKE %:kw%")
List<Post> search(@Param("status") int status, @Param("kw") String keyword);
@Query("SELECT COUNT(p) FROM Post p WHERE p.user.id = :userId")
long countByUser(@Param("userId") Long userId);
@Query("SELECT p FROM Post p WHERE p.createdAt >= :since ORDER BY p.createdAt DESC")
List<Post> findSince(@Param("since") LocalDateTime since);
}和 Eloquent 的对应
@Query("SELECT p FROM Post p WHERE p.status = :status") ≈
php
Post::where('status', $status)->get();区别:JPQL 用实体名和字段名(p.status、p.user.id),不是表名/列名。Spring Data 会把它翻译成对应数据库的 SQL。
原生 SQL:nativeQuery
要写数据库专属语法(窗口函数、GROUP_CONCAT、复杂 JOIN),用原生 SQL:
java
@Query(value = "SELECT * FROM posts WHERE status = :status LIMIT :limit",
nativeQuery = true)
List<Post> findRecent(@Param("status") int status, @Param("limit") int limit);TIP
原生 SQL 用表名和列名(posts、status),且结果映射到实体时要求列名能对应上字段。nativeQuery = true 后 JPQL 变原生 SQL,注意两者写法不同。
修改操作:@Modifying
增删改的 @Query 要加 @Modifying,并且通常配合事务:
java
public interface PostRepository extends JpaRepository<Post, Long> {
@Modifying
@Query("UPDATE Post p SET p.status = :status WHERE p.user.id = :userId")
int updateStatus(@Param("status") int status, @Param("userId") Long userId);
}TIP
updateStatus 返回受影响行数。加了 @Modifying 的方法必须跑在事务里,否则抛异常。调用方的 Service 方法记得加 @Transactional。
批量操作 vs 单条 save
| 操作 | 方式 |
|---|---|
| 循环 save 单条 | saveAll(list)(批量,一条条 INSERT) |
| 一次性 INSERT 多条 | @Modifying + INSERT INTO ... VALUES 多值 |
| 大表更新/删除 | @Modifying + JPQL,避免先查后改 |
java
// 批量保存(JpaRepository 自带)
postRepository.saveAll(posts);动态查询:Specification / Query by Example
条件不确定(搜索、筛选)时的动态 SQL:
Specification(进阶,JPA 官方的条件构建器)
java
@Service
public class PostService {
private final PostRepository repo;
public List<Post> search(String title, Integer status) {
Specification<Post> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (title != null && !title.isBlank()) {
predicates.add(cb.like(root.get("title"), "%" + title + "%"));
}
if (status != null) {
predicates.add(cb.equal(root.get("status"), status));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
return repo.findAll(spec);
}
}TIP
Specification ≈ Eloquent 的动态条件数组 / 查询作用域(scope)。对应 Laravel:
php
$query = Post::query();
if ($title) $query->where('title', 'like', "%$title%");
if ($status) $query->where('status', $status);
return $query->get();Repository 需要继承 JpaSpecificationExecutor<Post> 才有 findAll(spec) 方法。
Query by Example(简单场景)
java
Post example = new Post();
example.setStatus(1);
List<Post> result = repo.findAll(Example.of(example)); // 非 null 字段作为条件适合「按几个可空字段过滤」的简单场景,复杂逻辑还是 Specification。
分页 + 排序
java
Page<Post> page = postRepository.findByStatus(
1, PageRequest.of(0, 15, Sort.by(Sort.Direction.DESC, "createdAt")));排序 + 关联(小心列名):
java
@Query("SELECT p FROM Post p JOIN FETCH p.user ORDER BY p.createdAt DESC")
Page<Post> findAllWithUser(Pageable pageable);什么时候用什么
| 场景 | 用什么 |
|---|---|
| 单字段/多字段等值查询 | 方法名查询 findByXxx |
| 模糊搜索、日期范围 | @Query JPQL |
| 复杂 SQL、数据库专属语法 | @Query 原生 SQL |
| 条件动态组合 | Specification |
| 简单可空字段过滤 | Query by Example |
| 大列表更新/删除 | @Modifying |
方法名 vs @Query 怎么选
- 简单等值/单表 → 方法名(代码即文档)
- 复杂关联、统计、跨表 →
@Query - 一个规则:超过两三个条件的查询,直接写
@Query,别硬拼方法名