使用@EmbeddedID复合键的JPA仓库嵌套属性查询返回空值问题
问题描述
在基于@EmbeddedID实现复合键的JPA实体场景中,调用JPA仓库的嵌套属性查询方法(findByMyPk_Entity1_Id)返回的列表元素数量正确,但所有元素为空;而调用findAll方法却能正常获取完整实体列表。
相关代码
MySQL视图
CREATE OR REPLACE VIEW my_view AS SELECT col1, col2, col3, col4, col5, col6 FROM my_table
JPA实体类
@Entity @Table(name = "my_view") @Getter @Setter public class MyEntity { @EmbeddedId private MyPk myPk; @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "col1", insertable = false, updatable = false) private Entity1 entity1 ; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "col2", insertable = false, updatable = false) private Entity2 entity2; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "col3", insertable = false, updatable = false) private Entity3 entity3; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "col4", insertable = false, updatable = false) private Entity4 entity4; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "col5", insertable = false, updatable = false) private Entity5 entity5; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "col6", insertable = false, updatable = false) private Entity6 entity6; }
嵌入式ID类
@NoArgsConstructor @EqualsAndHashCode @AllArgsConstructor @Embeddable public class MyPk implements Serializable { @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "col1", insertable = false, updatable = false) private Entity1 entity1 ; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "col2", insertable = false, updatable = false) private Entity2 entity2; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "col3", insertable = false, updatable = false) private Entity3 entity3; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "col4", insertable = false, updatable = false) private Entity4 entity4; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "col5", insertable = false, updatable = false) private Entity5 entity5; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "col6", insertable = false, updatable = false) private Entity6 entity6; }
JPA仓库接口
public interface MyEntityRepository extends JpaRepository<MyEntity, MyPk> { List<MyEntity> findByMyPk_Entity1_Id(Long id); }
补充信息
- Entity1包含id属性
- 尝试过
@IdClass等复合键方案,均出现运行时错误 - 当前方案无运行时错误,但调用
findByMyPk_Entity1_Id返回空元素,仅数量正确 - 无法使用序列或唯一列方案,尝试
@MapIds也返回空元素 - 使用
findAll方法可在调试器中获取完整实体列表 - 环境版本:spring-data-jpa:1.9.4、hibernate-jpa-2.1-api:1.0.0、Java 8(遗留项目)
问题原因及解决方案
核心原因
嵌入式ID(@Embeddable)类中使用@ManyToOne关联实体违反JPA规范——复合主键必须由基本数据类型(如Long、String)或可序列化的简单类型组成,不能直接关联实体。这种错误映射会导致Spring Data JPA查询时,无法正确将数据库字段值映射到实体的复合主键及关联属性,最终出现元素数量正确但内容为空的情况。
修复步骤
- 重构嵌入式ID类:将
MyPk中的实体关联替换为对应视图列的基本类型字段:
@NoArgsConstructor @EqualsAndHashCode @AllArgsConstructor @Embeddable public class MyPk implements Serializable { private Long col1; private Long col2; private Long col3; private Long col4; private Long col5; private Long col6; }
注:字段类型需与数据库中col1-col6的实际类型匹配,比如字符串类型用String。
- 调整实体类关联映射:保留
MyEntity中的@ManyToOne关联,明确referencedColumnName对应关联实体的主键:
@Entity @Table(name = "my_view") @Getter @Setter public class MyEntity { @EmbeddedId private MyPk myPk; @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "col1", insertable = false, updatable = false, referencedColumnName = "id") private Entity1 entity1 ; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "col2", insertable = false, updatable = false, referencedColumnName = "id") private Entity2 entity2; // 其余关联字段按相同规则调整 }
- 修改仓库查询方法:基于主键的基本类型字段编写查询,或用
@Query自定义更清晰的查询逻辑:
public interface MyEntityRepository extends JpaRepository<MyEntity, MyPk> { // 方式一:基于主键字段的嵌套查询 List<MyEntity> findByMyPk_Col1(Long id); // 方式二:自定义JPQL查询 @Query("SELECT e FROM MyEntity e WHERE e.entity1.id = :id") List<MyEntity> findByEntity1Id(@Param("id") Long id); }
额外说明
- 原方案中
findAll能正常工作,是因为Hibernate加载全量数据时会直接映射结果集到实体所有字段;而嵌套属性查询时,Spring Data JPA会尝试通过复合主键的关联实体构建逻辑,因主键映射错误导致无法填充数据。 - 对于低版本Spring Data JPA(1.9.x)的遗留项目,严格遵循JPA规范的映射方式能避免绝大多数异常映射问题。
内容的提问来源于stack exchange,提问作者amvik13
相关产品推荐
相关产品推荐

