当前位置: 首页 > 知识库问答 >
问题:

Hibernate线程安全幂等更新插入,无需约束异常处理?

冯胤
2023-03-14

我有一些代码来执行一个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);
            }
        }
    }
}

最初对s0的搜索是不令人满意的:

  • HiberNate幂等更新-概念上类似但更简单的场景,不考虑多线程或多处理。
  • HiberNate可以使用MySQL的“ON DUPLICATE KEY UPDATE”语法吗?更好的是,通过使用@SQLIn
  • 如何使用HiberNate来模仿更新插入行为?与上面的问题非常相似,答案相似
  • HiberNate"ON DUPLICATE KEY"逻辑同上,回答提到合并()单线程时可以
  • 使用HiberNate批量插入或更新?类似的问题,但选择的答案是脱轨的,使用存储过程
  • 防止使用JPA违反唯一约束的最佳方法再次非常天真,面向单线程的问答

这是一个死胡同,因为我真的不理解这条评论,尽管它听起来像是一个聪明的解决方案,而且提到了“实际相同的SQL语句”。

另一个有希望的方法是:Hibernate和 Spring 在提交到 DB 之前修改查询

冲突时不执行任何操作/重复密钥更新

两个主要的开源数据库都支持将幂等性向下推送到数据库的机制。下面的例子使用PostgreSQL语法,但可以很容易地适应MySQL。

通过遵循Hibernate和Spring中的思路在提交到DB之前修改查询,挂钩到Hibernate的查询生成,以及我如何在Hibernate中配置StatementInspector?,我实现了:

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 . spring framework . ORM . hibernate 5 . hibernateoptimisticlockingfailure异常:批处理更新从更新[0]返回了意外的行数;实际行数:0;预期:1;嵌套异常为org . hibernate . stalestateexception:批处理更新从更新[0]返回了意外的行数;实际行数:0;预期:1

这是有道理的,如果您考虑一下在幕后发生了什么:ON CONFLICT DO NOY导致插入零行,但需要插入一次。

是否有一种解决方案能够实现线程安全的无异常并发幂等插入,并且不需要手动定义整个SQL插入语句以由Hibernate执行?

值得一提的是,我认为将重复数据检查向下推到数据库的方法是找到正确解决方案的途径。

澄清由< code>batchInsert方法使用的< code>IncomingItem对象来自记录不可变的系统。在这种特殊情况下,< code>ON CONFLICT DO NOTHING的行为与UPSERT相同,尽管可能会丢失第n次更新。

共有3个答案

燕成双
2023-03-14

我假设根据您的帖子,源、系统id和更新是一个唯一的键。基于此。我会的

  • 使用一个查询检索传入项目的列表。(我假设你在这个数据库中没有 100 万条记录)
  • 将唯一键与您的列表进行比较,并保留要插入的键。
  • 保存项目

一些伪代码:

public void batchInsert(IncomingItem[] items) {
    //get all IncomingItem from the DB
    List<IncomingItem> incomingItems = //DB query findAll;
    List<IncomingItem> incomingItemsToSave = new ArrayList<>();
    //check your duplicates!
    for(IncomingItem incomingItem : incomingItems){
        Arrays.stream(items).filter(item -> {
            //compare unique key
            // ...  code here ...
            if(!same unique key){
                incomingItemsToSave.add(item);
            }
        });
    }

    try(Session session = sessionFactory.openSession()) {
        batchInsert(session, incomingItemsToSave);
    }
    catch(PersistenceException e) {

    }
}
霍建柏
2023-03-14

注意,“幂等”与“冲突忽略”不同。后者可能会导致忽略对数据库的第二次写入,即使在插入失败时它实际上应该进行更新。

是否有一种解决方案可以启用html" target="_blank">线程安全的无异常并发幂等插入

我想说的是,如果没有RDBMS的具体支持,这在理论上可能是不可能的,尤其是“并发”部分。原因是数据不会实际写入,甚至可能“可见”,直到事务提交。那么,如果在事务 A 中确定记录不存在并且完成 INSERT 会发生什么。即使该 INSERT 对其他事务立即且原子可见,并发事务 B 也会确定它应该执行 UPDATE。现在,如果以后的事务 A 遇到导致其回滚的问题怎么办?事务 A 中的插入数据消失,事务 B 的 UPDATE 将找不到任何要更新的记录。

这就是为什么“并发”部分通常不工作的一个原因,因为不是所有的RDBMSs都支持某种原子的< code>UPSERT(或“on conflict ignore”)。

但是,您似乎不介意丢失对同一记录的第二次写入(更新),因为您正在谈论幂等性,这意味着潜在的UPDATE实际上不会修改记录的数据,如果它已经存在。在这种情况下,“冲突忽略”确实等同于幂等性。

一个(显而易见?“解决方案”是使用一些显式锁(在数据库中)进行互斥,即事务 A 获取锁,执行其操作,然后再次释放它。事务 B 尝试获取锁,但在事务 A 完成之前将被阻止。但是,这将减少或阻止并发性,尤其是在一个事务中处理大量记录时。另外,由于RDBMS不知道锁与其保护的记录之间的关系,因此锁只是建议性的,每个客户端都必须使用相同的锁定方案。

你说你想“将幂等性向下推送到数据库”。如果这不是一个严格的要求,你可以只控制 Java 代码中的并发性;例如,通过使用一些支持并发的集合,其中代码以原子方式检查并插入它将要写入 RDBMS 的每个数据项的 ID。如果 ID 已在集合中,请跳过该项,否则插入到数据库中。

龚博涛
2023-03-14

简短回答 - Hibernate不支持开箱即用(正如Hibernate大师在这篇博文中证实的那样)。也许你可以使用你已经描述的机制在某些情况下让它在某种程度上工作,但为此目的,直接使用本机查询对我来说似乎是最直接的方法。

更长的答案是,考虑到我猜 Hibernate 的所有方面,很难支持它,例如:

  • 如何处理发现重复项的实例,因为它们应该在持久化后成为托管实例?将它们合并到持久性上下文中?
  • 如何处理已经保留的关联,对其应用哪些级联操作(持久/合并/something_new;或者此时做出该决定是否为时已晚)?
  • 数据库是否从 upsert 操作返回足够的信息以涵盖所有用例(跳过的行;在批处理插入模式下未跳过的生成键等)。
  • @Audit实体呢,它们是创建还是更新,如果更新了什么?
  • 还是版本控制和乐观锁定(根据定义,在这种情况下您实际上希望例外)?

即使Hibernate以某种方式支持它,我不确定如果有太多的警告需要注意和考虑,我是否会使用该功能。

所以,我遵循的经验法则是:

  • 对于简单的场景(大多数时候):持续重试。在特定错误的情况下重试(通过异常类型或类似)可以使用类似AOP的方法(注释、自定义拦截器和类似)进行全局配置,具体取决于您在项目中使用的框架,无论如何这都是一个很好的实践,尤其是在分布式环境中。
  • 对于复杂的场景和性能密集型操作(尤其是在批处理、非常复杂的查询等方面):本机查询以最大限度地利用特定的数据库功能。
 类似资料:
  • 我正在使用Spring和Hibernate,我得到了这个例外。以下是我试图获得的:我有User和UserSettings类,它们以OneToMany和ManyToOne注释为界,如下所示: 现在,我想为用户添加设置,我是这样做的: 问题来了:UserSettings中的用户持有旧设置和新设置的集合(设置,我刚刚创建并想要添加),但旧设置持有用户的集合,其中没有新设置。我想这就是为什么我会遇到异常,

  • 我在elasticsearch插件中遇到了异常: java.security.AccessControlExcture:访问拒绝(org.elasticsearch.ThreadPersionorg.elasticsearch.ThreadPersion)java.security.AccessControlContext.check权限(AccessControlContext.java:472

  • 我有一张这样定义的桌子 我如何定义排除约束,以便只有一行具有特定

  • 我知道自动布局链基本上由3个不同的过程组成。 更新约束 布局视图(这里是我们计算框架的地方) 显示 我并不完全清楚的是 和 之间的内在区别。来自苹果文档: 设置需要布局 当您想要调整视图子视图的布局时,请在应用程序的主线程上调用此方法。此方法记下请求并立即返回。由于此方法不会强制立即更新,而是等待下一个更新周期,因此您可以使用它来使多个视图的布局失效,然后再更新这些视图中的任何一个。此行为允许您将

  • 问题内容: 我试着通过网络搜索,但是徒劳。有没有一种方法可以使用hibernate来执行幂等更新。 一种用例是使用HTTP PUT通过REST API更新数据库中的特定字段。因此,例如,如果我有一个包含列: _ Id,Name,Phone,UpdateDate*_ 的数据库 _ ,_ 并且我多次用相同的值更新(特定 ID的 ) Phone ( 电话) 字段,那么只有我的第一个操作必须更新 Phon

  • 我刚开始使用hibernate编程,请帮帮我,我在执行下面的文件时遇到了问题。我使用的是MySQL数据库。我的错误是 SLF4J:未能加载类“org.slf4j.impl.StatibloggerBinder”。slf4j:默认为无操作(NOP)记录器实现slf4j:有关更多细节,请参见http://www.slf4j.org/codes.html#staticloggerbinder。Hiber