文档站点
Skip to content

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,别硬拼方法名

面向 PHP / Laravel 开发者的 Spring Boot 中文文档