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
相关产品推荐
相关产品推荐

