休眠线程安全幂等的升级插入没有约束异常处理?
我有一些执行UPSERT的代码,也称为合并。我想清理这段代码,具体来说,我想摆脱异常处理,并减少代码的整体冗长性和复杂性,以实现如此简单的操作。要求是插入每个项目,除非它已经存在:
public void batchInsert(IncomingItem[] items) {
try(Session session = sessionFactory.openSession()) {
batchInsert(session, items);
}
catch(PersistenceException e) {
if(e.getCause() instanceof ConstraintViolationException) {
logger.warn("attempting to recover from constraint violation");
DateTimeFormatter dbFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
items = Arrays.stream(items).filter(item -> {
int n = db.queryForObject("select count(*) from rets where source = ? and systemid = ? and updtdate = ?::timestamp",
Integer.class,
item.getSource().name(), item.getSystemID(),
dbFormat.format(item.getUpdtDateObj()));
if(n != 0) {
logger.warn("REMOVED DUPLICATE: " +
item.getSource() + " " + item.getSystemID() + " " + item.getUpdtDate());
return false;
}
else {
return true; // keep
}
}).toArray(IncomingItem[]::new);
try(Session session = sessionFactory.openSession()) {
batchInsert(session, items);
}
}
}
}
SO 的初始搜索不令人满意:
- 休眠幂等更新 - 概念上相似但更简单的方案,不考虑多线程或多处理。
-
Hibernate可以使用MySQL的“ON DUPLICATE KEY UPDATE”语法吗?更好的是,通过使用注释将原子性推送到数据库来消除争用条件;遗憾的是,此解决方案太容易出错,无法在更宽的表上使用,并且在不断发展的应用程序中需要大量维护。
@SQLInsert
- 如何使用Hibernate模仿upsert行为?与上述问题非常相似,答案相似
-
休眠+“ON DUPLICATE KEY”逻辑与上述相同,答案提到单线程时可以接受
merge()
- 批量插入或使用休眠进行更新?类似的问题,但选择的答案是偏离轨道的,使用存储过程
- 防止JPA再次违反独特约束的最佳方法 非常幼稚,面向单线程的问题和答案
在被标记为重复的Spring Data JPA中如何进行重复密钥更新的问题中,我注意到了这个有趣的评论:
这是一个死胡同,因为我真的不理解这个评论,尽管它听起来像是一个聪明的解决方案,并提到了“实际相同的SQL语句”。
另一个有前途的方法是:Hibernate和Spring在提交到DB之前修改查询
冲突时不执行任何操作/重复密钥更新时
这两个主要的开源数据库都支持将幂等性向下推送到数据库的机制。下面的示例使用 PostgreSQL 语法,但可以很容易地适应 MySQL。
通过遵循Hibernate和Spring修改查询在提交到DB之前的想法,Hooking into Hibernate的查询生成,以及如何在Hibernate中配置DementInspector?,我实现了:
import org.hibernate.resource.jdbc.spi.StatementInspector;
@SuppressWarnings("serial")
public class IdempotentInspector implements StatementInspector {
@Override
public String inspect(String sql) {
if(sql.startsWith("insert into rets")) {
sql += " ON CONFLICT DO NOTHING";
}
return sql;
}
}
与属性
<prop key="hibernate.session_factory.statement_inspector">com.myapp.IdempotentInspector</prop>
不幸的是,当遇到重复项时,这会导致以下错误:
由以下原因引起: org.springframework.orm.hibernate5.HibernateOptimisticLockingFailureException: Batch update 从 update [0] 返回了意外的行计数;实际行数: 0;预期: 1;嵌套异常是 org.hibernate.StaleStateException: Batch update 从 update [0] 返回意外的行计数;实际行数: 0;预期: 1
这是有道理的,如果你考虑一下幕后发生的事情:导致插入零行,但需要一个插入。ON CONFLICT DO NOTHING
是否有一种解决方案可以启用线程安全的无异常并发幂等插入,并且不需要手动定义要由Hibernate执行的整个SQL插入语句?
就其价值而言,我认为将dupcheck向下推到数据库的方法是通往正确解决方案的途径。
澄清该方法使用的对象源自记录不可变的系统。在此特殊条件下,其行为与 UPSERT 相同,尽管可能会丢失第 N 次更新。IncomingItem
batchInsert
ON CONFLICT DO NOTHING