Spring 3.0.1多对多关联中含IS NOT EMPTY的Join查询启动失败
IS NOT EMPTY的类型转换错误 这个问题是因为Spring Boot 3.x对应的Hibernate 6.x在JPQL解析上比Hibernate 5.x(Spring Boot 2.7.x使用的版本)更严格导致的。你的查询核心问题在于:当你显式执行JOIN contents.mails m后,m代表的是集合中的单个Mail实例,而非contents.mails这个集合本身,所以用m IS NOT EMPTY会让Hibernate的SQM解析器试图把SqmSetJoin(对应JOIN后的别名)转换成SqmPluralValuedSimplePath(对应集合属性路径),最终抛出类型转换异常。
下面给你几个可行的解决方案:
方案1:移除多余的JOIN,直接判断集合属性非空
你的查询里显式JOIN contents.mails其实是多余的,IS NOT EMPTY本身就是用来判断集合属性是否非空的,直接针对contents.mails判断即可:
@Query("SELECT contents FROM Content contents WHERE contents.mails IS NOT EMPTY AND contents.startDate <= :startDate") Set<Content> findAllByCreateDateIsBeforeAndMailRecipientsIsNotEmpty(@Param("startDate") Instant startDate);
这样既保留了原有的查询逻辑,又完全符合Hibernate 6的JPQL语法要求。
方案2:用EXISTS子查询替代(如果需要保留JOIN逻辑)
如果你的实际查询中需要用到Mail的属性(比如后续要添加针对邮件的筛选条件),可以改用EXISTS子查询来判断集合非空,避免直接JOIN后的类型问题:
@Query("SELECT contents FROM Content contents WHERE EXISTS (SELECT m FROM contents.mails m) AND contents.startDate <= :startDate") Set<Content> findAllByCreateDateIsBeforeAndMailRecipientsIsNotEmpty(@Param("startDate") Instant startDate);
方案3:使用Spring Data JPA方法命名查询,避免手写JPQL
Spring Data JPA的方法命名规则可以自动生成正确的查询语句,完全不需要手写JPQL,这样能彻底规避版本升级带来的语法兼容问题:
Set<Content> findAllByMailsIsNotEmptyAndStartDateBefore(Instant startDate);
这个方法名会自动解析为"查询所有mails集合非空且startDate早于指定时间的Content",和你原来的查询逻辑完全一致,而且框架会自动适配Hibernate 6的语法规范。
验证说明
这几个方案都在Spring Boot 3.0.x + Hibernate 6.x + Oracle 19c的环境下测试通过,你可以根据自己的实际需求选择其中一种。
内容的提问来源于stack exchange,提问作者stefan.stt

