使用Hibernate更新双向一对一关联对象时遇EntityExistException及异常SQL问题
解决Hibernate双向一对一关联更新的那些坑
兄弟,我太懂你这种Hibernate双向一对一踩坑的痛苦了!之前我也遇到过更新时要么抛EntityExistException,要么SQL先插新记录再删旧记录的离谱情况,咱们一步步拆解问题,搞定它:
一、先把双向一对一的映射配置搞对
双向一对一的核心是明确谁是关联的维护方,Hibernate靠这个来判断怎么生成SQL,配置错了直接导致重复操作。
正确的配置示例
假设你的关联实体是AccountExtrasModel,那两边的映射应该这么写:
AccountModel(维护方,负责管理外键)
import lombok.*; import javax.persistence.*; @ToString @NoArgsConstructor @Getter @Setter @Entity @Table(name = "Account") public class AccountModel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long accountId; // 建议用小驼峰命名,符合Java规范,别用Account_ID这种 @Column(nullable = false) private String username; // 关键配置:cascade=ALL级联所有操作,orphanRemoval=true自动删除 orphan 实体 @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) @JoinColumn(name = "extras_id", unique = true) // unique=true保证一对一约束 private AccountExtrasModel accountExtras; // 加个辅助方法,自动双向绑定,避免手动设置时漏了另一边 public void setAccountExtras(AccountExtrasModel accountExtras) { this.accountExtras = accountExtras; if (accountExtras != null) { accountExtras.setAccount(this); } } }
AccountExtrasModel(被维护方,不管理外键)
import lombok.*; import javax.persistence.*; @ToString @NoArgsConstructor @Getter @Setter @Entity @Table(name = "Account_Extras") public class AccountExtrasModel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long extrasId; private String address; private String phone; // 关键:用mappedBy指定维护方的属性名,告诉Hibernate我不负责关联维护 @OneToOne(mappedBy = "accountExtras") private AccountModel account; }
常见配置错误
- 两边都没加
mappedBy:Hibernate会把两边当成独立的关联,更新时就会先插新的关联记录再删旧的 - cascade设置不全:比如只加了
PERSIST没加MERGE,导致更新关联实体时Hibernate不知道要合并 - 没加
unique=true:这样数据库层面没法保证一对一,容易出现数据混乱
二、解决EntityExistException的更新姿势
这个错误90%是因为你在操作**游离状态(detached)**的实体时用错了方法。
正确的更新方式
方式1:先加载持久化实体再修改(推荐)
从数据库把实体捞出来(此时是managed状态),改完直接提交事务,Hibernate会自动同步:
@Transactional public void updateAccount(Long accountId, AccountModel updateRequest) { // 先从数据库加载,拿到持久化状态的实体 AccountModel managedAccount = entityManager.find(AccountModel.class, accountId); if (managedAccount == null) { throw new RuntimeException("账号不存在"); } // 更新基本属性 managedAccount.setUsername(updateRequest.getUsername()); // 更新关联的AccountExtras AccountExtrasModel updateExtras = updateRequest.getAccountExtras(); if (updateExtras != null) { if (managedAccount.getAccountExtras() != null) { // 已有关联,直接改属性 managedAccount.getAccountExtras().setAddress(updateExtras.getAddress()); managedAccount.getAccountExtras().setPhone(updateExtras.getPhone()); } else { // 没有关联,新建并双向绑定 AccountExtrasModel newExtras = new AccountExtrasModel(); newExtras.setAddress(updateExtras.getAddress()); newExtras.setPhone(updateExtras.getPhone()); managedAccount.setAccountExtras(newExtras); } } else { // 要移除关联的话,直接设为null,orphanRemoval=true会自动删除对应的Extras managedAccount.setAccountExtras(null); } // 啥都不用干,事务提交时Hibernate自动执行更新SQL }
方式2:用merge()处理游离实体
如果必须用前端传过来的游离实体更新,别用persist()或者save(),要用merge():
@Transactional public AccountModel updateDetachedAccount(AccountModel detachedAccount) { // merge会返回一个持久化状态的实体,原来的detachedAccount还是游离的 AccountModel managedAccount = entityManager.merge(detachedAccount); // 后续操作要基于返回的managedAccount return managedAccount; }
错误操作示例(别这么干!)
// 错误:直接对游离实体调用persist(),会抛EntityExistException entityManager.persist(detachedAccount);
三、为啥会出现“插入后再删除”的骚操作?
这种情况基本是因为关联关系没双向绑定,Hibernate识别不出关联的实体是已存在的,就会先插新的,再删旧的。
比如你只设置了account.setAccountExtras(extras),但没设置extras.setAccount(account),Hibernate就会认为这是个新的关联,搞出多余的SQL。
所以一定要用我上面写的setAccountExtras辅助方法,自动完成双向绑定,保证两边的关联关系一致。
内容的提问来源于stack exchange,提问作者Caciano
相关产品推荐
相关产品推荐

