Hibernate JPA多对多关联(@IdClass)级联持久化失败排查
问题描述
A和B是多对多关系,因关联需要携带属性,创建中间实体AB维护该关系。已为所有关联属性设置cascade=CascadeType.ALL,但保存全新的Transient状态A、AB、B时(建立关联后调用aRepository.save(a)),抛出反射相关异常。
异常信息
org.springframework.orm.jpa.JpaSystemException: Could not set field value [SHORT_CIRCUIT_INDICATOR] value by reflection : [class br.com.fisgar.crm.model.entities.PersonClientLead$ID.clientLead] setter of br.com.fisgar.crm.model.entities.PersonClientLead$ID.clientLead; nested exception is org.hibernate.PropertyAccessException: Could not set field value [SHORT_CIRCUIT_INDICATOR] value by reflection : [class br.com.fisgar.crm.model.entities.PersonClientLead$ID.clientLead] setter of br.com.fisgar.crm.model.entities.PersonClientLead$ID.clientLead at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:331) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528) at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59) at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213) at org.springframework.orm.jpa.JpaTemplate.execute(JpaTemplate.java:230)
数据模型代码
A类
@Entity public class A { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToMany(mappedBy = "a", cascade = CascadeType.ALL, orphanRemoval = true) private Set<AB> abSet = new HashSet<>(); }
B类
@Entity public class B { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToMany(mappedBy = "b", cascade = CascadeType.ALL, orphanRemoval = true) private Set<AB> abSet = new HashSet<>(); }
AB类
@Entity @IdClass(AB.ID.class) public class AB { @Id @ManyToOne @JoinColumn(name = "a_id") private A a; @Id @ManyToOne @JoinColumn(name = "b_id") private B b; @Getter @AllArgsConstructor @NoArgsConstructor public static class ID implements Serializable { private Long a; private Long b; //equals and hashcode goes here } }
执行代码
A a = new A(); B b = new B(); AB ab = new AB(); a.getAbSet().add(ab); b.getAbSet().add(ab); ab.setA(a); ab.setB(b); aRepository.save(a);
问题原因及解决方法
核心问题
使用@IdClass时,Hibernate需要在持久化AB前确定其ID值,但此时A、B处于Transient状态(未生成自增ID),Hibernate会用内部标记SHORT_CIRCUIT_INDICATOR尝试设置ID类的Long类型字段,导致反射赋值失败。同时@IdClass在关联实体作为复合主键的场景下,无法很好处理级联保存时的ID依赖问题。
修正方案
1. 改用@EmbeddedId替代@IdClass
重构AB实体,通过@EmbeddedId和@MapsId实现复合主键与关联实体的绑定:
@Entity public class AB { @EmbeddedId private ABId id; @MapsId("aId") @ManyToOne @JoinColumn(name = "a_id") private A a; @MapsId("bId") @ManyToOne @JoinColumn(name = "b_id") private B b; @Embeddable @Getter @Setter @AllArgsConstructor @NoArgsConstructor public static class ABId implements Serializable { private Long aId; private Long bId; // 实现equals和hashCode方法 } // 同步更新关联实体与嵌入ID的对应值 public void setA(A a) { this.a = a; if (id == null) { id = new ABId(); } id.setAId(a.getId()); } public void setB(B b) { this.b = b; if (id == null) { id = new ABId(); } id.setBId(b.getId()); } }
2. 调整关联逻辑(可选,更严谨)
确保关联关系双向绑定的顺序合理:
A a = new A(); B b = new B(); AB ab = new AB(); ab.setA(a); ab.setB(b); a.getAbSet().add(ab); b.getAbSet().add(ab); aRepository.save(a);
原理说明
@EmbeddedId允许Hibernate在关联实体(A、B)生成ID后,再同步更新嵌入ID中的对应字段值,避免了@IdClass中因ID依赖未生成值而导致的反射异常,能更好地支持级联保存场景。
内容的提问来源于stack exchange,提问作者Rafael Lima
相关产品推荐
相关产品推荐

