懒加载OneToOne关联时触发Hibernate EntityExistsException问题排查
问题描述
配置了Account与Budget的双向OneToOne关联并设置懒加载,创建新Account及关联Budget时触发EntityExistsException,移除@MapsId后异常消失但懒加载失效,求异常原因及解决办法。
实体代码
Account实体
@Audited @Entity public class Account { @Id @GeneratedValue(strategy = GenerationType.TABLE) protected long id; @OneToOne(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval=true, fetch=FetchType.LAZY) private Budget mainBudget; // 其他属性和方法省略 }
Budget实体
@Audited @Entity public class Budget { @Id @GeneratedValue(strategy = GenerationType.TABLE) protected long id; @OneToOne(fetch=FetchType.LAZY) @MapsId private Account account; @OneToMany(targetEntity = Budget.class, cascade = CascadeType.ALL, mappedBy = "parentBudget", orphanRemoval=true) @Fetch(value = FetchMode.SUBSELECT) private List<Budget> subBudget; // 其他属性和方法省略 }
异常信息
javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [[PACKACKENAMES].Account#50](31 internal lines) at org.apache.openejb.persistence.JtaEntityManager.merge(JtaEntityManager.java:203) at [PACKACKENAMES].DAOClass.create(DaoClass.java:75) at [PACKACKENAMES].DaoClass.create(DaoClass.java:66) // 后续栈帧省略
问题分析与解决
异常原因
@MapsId的作用是让Budget的主键复用Account的主键值,实现共享主键的一对一关联,但你给Budget的@Id同时配置了@GeneratedValue(strategy = GenerationType.TABLE),这就产生了冲突:
- 创建Account时,JPA会生成一个主键(比如#50)
- 创建关联的Budget时,
@MapsId要求用Account的#50作为自己的主键,但Budget的@Id生成策略又会尝试生成新的主键值 - 双向关联的
cascade = CascadeType.ALL会把两个实体都纳入持久化上下文,此时JPA发现Account和Budget试图使用同一个主键值#50,而Session中已存在Account实例,因此抛出EntityExistsException
移除@MapsId后,Budget会生成独立主键,异常消失,但此时JPA无法通过共享主键的优化逻辑实现可靠的懒加载,导致懒加载失效。
解决方案
1. 修正共享主键配置
既然用@MapsId实现共享主键,就要去掉Budget主键上的生成策略,让它完全复用Account的主键:
@Audited @Entity public class Budget { @Id protected long id; // 移除@GeneratedValue @OneToOne(fetch=FetchType.LAZY) @MapsId private Account account; // 其他代码不变 }
2. 维护双向关联一致性
创建Account和Budget时,必须手动维护双向关联的关系,避免持久化时状态不一致:
// DAO的create方法示例 public Account createAccountWithBudget() { Account account = new Account(); Budget budget = new Budget(); // 同时设置双向关联 account.setMainBudget(budget); budget.setAccount(account); entityManager.persist(account); // 借助cascade=ALL自动持久化Budget return account; }
3. 验证懒加载有效性
配置完成后,可通过以下方式验证懒加载:
- 在事务上下文外获取Account实例,调用
account.getMainBudget(),若触发SQL查询则说明懒加载正常(事务内JPA可能提前加载关联) - 查看JPA实现(如Hibernate)的SQL日志,确认仅在访问
mainBudget时才会执行查询Budget的SQL
内容的提问来源于stack exchange,提问作者lasbr
相关产品推荐
相关产品推荐

