使用JPA和Hibernate写入值相同的不同对象至表的问题
1. 当前结构下能否直接用saveAll()?
不行。原因是你给@ManyToOne设置了CascadeType.ALL,这意味着保存TableA时会级联处理关联的TableB。当调用saveAll()时,两个TableA里的TableB("idB1","nameB1")是不同的对象实例,但主键tB_id相同。Hibernate的Session不允许同时存在两个主键相同的持久化对象,因此会抛出A different object with the same identifier value was already associated with the session错误。
2. 无需解构输入列表的替代方案
方案一:调整级联策略(适用于TableB已存在于数据库的场景)
如果关联的TableB数据已经存在于数据库中,不需要级联保存TableB,可以把级联类型从CascadeType.ALL改为只级联必要操作,比如CascadeType.MERGE(需要更新TableB时)或直接移除级联(TableB无需通过TableA修改时):
@ManyToOne(cascade = [CascadeType.MERGE]) // 或直接移除cascade参数 @JoinColumn(name = "tB_id", referencedColumnName = "tB_id", nullable = false) val table2: TableB,
这样Hibernate会直接使用数据库中已有的TableB记录,不会尝试重复保存新实例,避免冲突。
方案二:提前去重TableB实例(通用场景)
调用saveAll()前,对输入列表中的TableB进行实例去重,确保相同tB_id的TableB使用同一个对象实例。用HashMap缓存tB_id对应的实例,遍历列表替换即可:
val tableBCache = mutableMapOf<String, TableB>() val processedTableAList = x.map { tableA -> val cachedTableB = tableBCache.getOrPut(tableA.table2.tB_id) { tableA.table2 } tableA.copy(table2 = cachedTableB) } tableARepo.saveAll(processedTableAList)
该操作仅遍历列表替换对象引用,无需解构业务逻辑,能保证同一个tB_id的TableB在Session中只有一个实例。
方案三:使用EntityManager.merge()替代saveAll()
Hibernate的merge()方法会先检查Session中是否存在相同主键的对象:存在则合并属性到已有持久化对象,不存在则创建新的持久化对象。可以自定义批量合并方法:
// 在Repository中注入EntityManager @Autowired private lateinit var entityManager: EntityManager fun saveAllWithMerge(tableAList: List<TableA>): List<TableA> { return tableAList.map { entityManager.merge(it) } }
调用该方法代替saveAll(),可自动处理相同主键的对象冲突,无需修改输入列表结构。
内容的提问来源于stack exchange,提问作者kartikay101

