You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JPA中实现带分页的LIKE模糊查询与关联IN查询的报错及解决方案

解决JPA多对多关联的分页查询问题

首先,你遇到的语法错误核心原因是:原生SQL里不能直接引用Java实体的集合属性activities——数据库只认识表和列,不知道这个集合对应的中间表;另外直接传入ActivityDao对象到原生SQL也是错误的,应该用它的主键ID来做关联查询。

下面给你三种可行的解决方案,按推荐程度排序:

方案1:使用JPQL查询(最推荐,符合JPA面向对象特性)

JPQL可以直接利用实体间的关联关系,不用手动写中间表关联,代码更简洁易维护:

@Query("SELECT c FROM ClassDao c " +
       "WHERE (LOWER(c.title) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR LOWER(c.description) LIKE LOWER(CONCAT('%', :searchTerm, '%'))) " +
       "AND :activity MEMBER OF c.activities")
Page<ClassDao> findBySearchTermAndActivity(@Param("searchTerm") String searchTerm, 
                                           @Param("activity") ActivityDao activity, 
                                           Pageable pageable);

说明:

  • 用MEMBER OF关键字直接判断activity是否属于c.activities集合,JPA会自动帮你关联中间表处理逻辑
  • 把原本的两个重复参数(titlePhrase和descriptionPhrase)合并成一个searchTerm,避免冗余传参
  • LOWER()和CONCAT()保证查询不区分大小写,同时支持前后模糊匹配

方案2:修正原生SQL查询(适合需要数据库特定语法的场景)

如果一定要用原生SQL,必须手动关联中间表db_joint_table__class_activities,同时要处理分页的统计查询:

@Query(value = "SELECT c.* FROM classes c " +
               "JOIN db_joint_table__class_activities j ON c.id = j.class_id " +
               "WHERE (LOWER(c.title) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR LOWER(c.description) LIKE LOWER(CONCAT('%', :searchTerm, '%'))) " +
               "AND j.activity_id = :activityId",
       countQuery = "SELECT COUNT(DISTINCT c.id) FROM classes c " +
                    "JOIN db_joint_table__class_activities j ON c.id = j.class_id " +
                    "WHERE (LOWER(c.title) LIKE LOWER(CONCAT('%', :searchTerm, '%')) OR LOWER(c.description) LIKE LOWER(CONCAT('%', :searchTerm, '%'))) " +
                    "AND j.activity_id = :activityId",
       nativeQuery = true)
Page<ClassDao> findBySearchTermAndActivityNative(@Param("searchTerm") String searchTerm, 
                                                 @Param("activityId") Long activityId, 
                                                 Pageable pageable);

说明:

  • 必须关联中间表来建立classes和activity的关系,用activity_id作为关联条件
  • 分页查询需要单独写countQuery,并且用COUNT(DISTINCT c.id)避免多对多关联产生的重复行导致统计错误
  • 这里传入的是activityId而非ActivityDao对象,原生SQL只能识别数据库的列值

方案3:使用Specification(适合动态组合查询条件的场景)

如果以后查询条件可能需要动态调整(比如新增过滤维度),用Specification会更灵活:

首先修改你的ClassRepo,让它继承JpaSpecificationExecutor:

public interface ClassRepo extends JpaRepository<ClassDao, Long>, JpaSpecificationExecutor<ClassDao> { }

然后构建查询Specification:

public Specification<ClassDao> buildClassSearchSpec(String searchTerm, ActivityDao activity) {
    return (root, query, criteriaBuilder) -> {
        // 构建标题/描述的模糊查询条件
        Predicate searchPredicate = criteriaBuilder.or(
            criteriaBuilder.like(criteriaBuilder.lower(root.get("title")), "%" + searchTerm.toLowerCase() + "%"),
            criteriaBuilder.like(criteriaBuilder.lower(root.get("description")), "%" + searchTerm.toLowerCase() + "%")
        );
        
        // 构建活动关联条件
        Predicate activityPredicate = criteriaBuilder.isMember(activity, root.get("activities"));
        
        // 组合两个条件
        return criteriaBuilder.and(searchPredicate, activityPredicate);
    };
}

使用的时候直接调用findAll方法:

Page<ClassDao> result = classRepo.findAll(buildClassSearchSpec(searchTerm, targetActivity), pageable);

额外优化建议

你的多对多关联现在两边都写了@JoinTable,这会导致JPA认为两边都是维护端,可能产生重复的中间表操作。建议只在一端维护关联,比如修改ActivityDao的关联注解:

@ManyToMany(mappedBy = "activities") // 表示这个关联由ClassDao的activities属性维护
@ToStringExclude @EqualsAndHashCode.Exclude
private Set<ClassDao> classes;

这样ClassDao是关联的维护端,ActivityDao是被维护端,更符合JPA的规范。

内容的提问来源于stack exchange,提问作者CR Sardar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 17:24:05