Hibernate merge操作导致@ElementCollection出现重复条目问题
Hibernate @ElementCollection合并时重复插入元素的问题
定义的实体类
@Entity public class TestEntity { private List<String> collection; private String id; @Id @Column(columnDefinition = "varchar(50) COLLATE utf8_bin") public String getID() { return id; } public void setID(String value) { this.id = value; } @ElementCollection public List<String> getCollection() { return this.collection; } public void setCollection(List<String> collection) { this.collection = collection; } }
测试代码
String newId = UUID.randomUUID().toString(); TestEntity entity = new TestEntity(); entity.setCollection(new ArrayList<>()); entity.setID(newId); EntityManagerFactory emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME); EntityManager em = emf.createEntityManager(); EntityTransaction transaction = em.getTransaction(); transaction.begin(); em.persist(entity); transaction.commit(); em.close(); for(int i=0; i<4; i++) { entity.getCollection().add(UUID.randomUUID().toString()); em = emf.createEntityManager(); transaction = em.getTransaction(); transaction.begin(); TestEntity merged = em.merge(entity); transaction.commit(); em.close(); }
问题与现象
循环中每次为entity的collection添加新条目后执行merge并提交,期望Hibernate生成如下SQL(以第二次循环为例):
delete from TestEntity_conllection where TestEntity_ID='ff9c495b-2707-4004-8123-e470cc71e779' insert into TestEntity_conllection (TestEntity_ID, connectionIDs) values ('ff9c495b-2707-4004-8123-e470cc71e779', '84628fb3-c516-497f-877e-9bbb02e79be1') insert into TestEntity_conllection (TestEntity_ID, connectionIDs) values ('ff9c495b-2707-4004-8123-e470cc71e779', '439b0edb-afd1-4cec-bc0e-9730da4f51ae')
但实际缺失第一条delete语句,导致Hibernate不断添加重复条目,最终testentity_collection表出现大量重复数据:
| TestEntity_ID | collection |
|---|---|
| ff9c495b-2707-4004-8123-e470cc71e779 | 84628fb3-c516-497f-877e-9bbb02e79be1 |
| ff9c495b-2707-4004-8123-e470cc71e779 | 84628fb3-c516-497f-877e-9bbb02e79be1 |
| ff9c495b-2707-4004-8123-e470cc71e779 | 439b0edb-afd1-4cec-bc0e-9730da4f51ae |
| ff9c495b-2707-4004-8123-e470cc71e779 | 84628fb3-c516-497f-877e-9bbb02e79be1 |
| ff9c495b-2707-4004-8123-e470cc71e779 | 439b0edb-afd1-4cec-bc0e-9730da4f51ae |
| ff9c495b-2707-4004-8123-e470cc71e779 | c6bb7ef1-f679-4b1c-bdfc-06ed1e12e60e |
| ff9c495b-2707-4004-8123-e470cc71e779 | 84628fb3-c516-497f-877e-9bbb02e79be1 |
| ff9c495b-2707-4004-8123-e470cc71e779 | 439b0edb-afd1-4cec-bc0e-9730da4f51ae |
| ff9c495b-2707-4004-8123-e470cc71e779 | c6bb7ef1-f679-4b1c-bdfc-06ed1e12e60e |
| ff9c495b-2707-4004-8123-e470cc71e779 | 15edb341-c6c3-433e-8d1f-c624844e5d08 |
以下两种情况不会出现该问题:
- 将
entity.setCollection(new ArrayList<>());移至第一次事务提交之后; - 初始时为
collection设置至少一个元素的List而非空List。
解答
这不是Hibernate的bug,是对Hibernate处理空集合的机制理解不到位导致的问题。
核心原因:当持久化一个带有空ArrayList的实体时,Hibernate会判定这个集合处于"未初始化"状态。后续执行merge时,Hibernate对比内存实体与数据库状态,会误以为是在原有空集合的基础上追加新元素,而非更新整个集合——因为它没有记录下初始空集合的状态,只会插入新增元素,不会先删除原有数据。
你提到的两种规避方法本质都是让Hibernate正确识别集合的初始状态:
- 把
entity.setCollection(new ArrayList<>());移到第一次事务提交后:此时实体已脱离持久化上下文,后续初始化集合时,Hibernate会将其标记为"已初始化",merge时会完整对比集合内容,执行先删后插的逻辑。 - 初始给集合至少一个元素:Hibernate会明确记录集合的非空初始状态,后续
merge时能正确识别集合变化,从而执行先删后插。
正确用法建议
- 在实体类中直接初始化集合,避免空集合状态被误判:
@Entity public class TestEntity { private List<String> collection = new ArrayList<>(); // 其他属性与方法... }
- 或者在修改集合前,先从数据库加载最新的托管实体,再修改并提交,而非操作脱离上下文的旧实体:
for(int i=0; i<4; i++) { em = emf.createEntityManager(); transaction = em.getTransaction(); transaction.begin(); TestEntity managedEntity = em.find(TestEntity.class, newId); managedEntity.getCollection().add(UUID.randomUUID().toString()); transaction.commit(); em.close(); }
内容的提问来源于stack exchange,提问作者chklump
相关产品推荐
相关产品推荐

