Spring Boot 3.0.1升级后@Query结合@EmbeddedId查询报错
这是Spring Boot 3.x升级后很常见的Hibernate ORM版本变更引发的问题,我帮你拆解下背后的原因和解决方向:
核心原因:Hibernate 6.x对JPQL解析的重大重构
Spring Boot 3.0.x对应的是Hibernate 6.1.x,而2.7.x用的是Hibernate 5.6.x。这两个版本之间,Hibernate对JPQL/HQL的解析引擎做了彻底重写,其中嵌入式ID(@EmbeddedId)的属性访问规则变化是你遇到这个问题的关键:
1. 属性路径解析的严格性提升
在Hibernate 5.x中,你可以相对宽松地在@Query里直接引用@EmbeddedId的字段(比如where s.scenarioId = :id),解析器会自动关联到当前实体的嵌入式ID类。但Hibernate 6.x对属性路径的匹配逻辑更严格,当项目中存在多个@EmbeddedId类(比如你提到的ScenarioPK和ConfigurationDomainPK)时,解析器可能会因为字段名重叠或者路径模糊,错误地关联到无关的嵌入式ID类,从而抛出指向ConfigurationDomainPK的错误。
2. 类型歧义导致的错误指向
如果ScenarioPK和ConfigurationDomainPK有同名的字段,或者你的JPQL语句中属性路径的写法不够明确,Hibernate 6的解析器会在候选的属性路径中匹配错误的类型,最终把锅甩给了无关的ConfigurationDomainPK类,这就是错误提示看起来莫名其妙的原因。
3. 宽松语法不再被兼容
Hibernate 6.x移除了很多5.x中允许的“语法糖”,比如省略实体到嵌入式ID的完整路径。你提到传入完整ScenarioPK参数能正常运行,是因为直接绑定整个嵌入式ID对象时,解析器不需要拆解字段路径,避开了这个解析歧义的问题。
解决思路和验证方法
针对这个问题,你可以按以下步骤排查和解决:
明确指定完整的属性路径:在@Query中确保写出从实体到嵌入式ID字段的完整路径,比如你的Scenario实体的@EmbeddedId属性叫
id,那么JPQL应该写成:@Query("select s from Scenario s where s.id.scenarioId = :scenarioId") List<Scenario> findByScenarioId(@Param("scenarioId") Long scenarioId);绝对不要省略
s.id这部分,避免解析器混淆。检查嵌入式ID类的字段冲突:对比ScenarioPK和ConfigurationDomainPK的字段名,如果有同名字段(比如都叫
domainId),可以考虑给其中一个类的字段添加@AttributeOverride来重命名,消除歧义:@Entity public class Scenario { @EmbeddedId @AttributeOverride(name = "domainId", column = @Column(name = "scenario_domain_id")) private ScenarioPK id; // ...其他属性 }开启Hibernate查询解析日志:添加日志配置,开启
org.hibernate.query.hql的DEBUG级别,查看JPQL解析的详细过程,能直观看到解析器是如何匹配属性路径的,帮你定位具体的歧义点。升级到更高版本的Spring Boot:Spring Boot 3.0.1是3.x系列的早期版本,后续的补丁版本(比如3.0.10)或者3.1+版本已经修复了不少Hibernate解析器的bug,升级后可能自动解决这个问题。
内容的提问来源于stack exchange,提问作者Fernando Lopes

