【问题标题】:Hibernate: Insert objects with unique constraints from different threadsHibernate:插入来自不同线程的具有唯一约束的对象
【发布时间】:2014-07-15 19:06:46
【问题描述】:

我有以下问题。我有 10 个线程创建插入数据库的对象。每个线程都有一个 ThreadLocal 和它自己的会话。所有对象在创建后都插入在一起。这些对象有一个标记为唯一的列。但是,我有一个问题,两个不同的线程可能会创建同一个对象。这种行为是需要的,但我不知道如何将它们插入到我的数据库中。

目前,每个线程都会查询所有插入数据库的对象,检查查询的对象是否存在,并将不存在的对象插入数据库。但是,由于在所有对象的查询中都可能不存在该对象,因此当我插入对象并且它们已经被另一个线程添加时,我会得到一个 ConstraintViolationException。但是,为每个对象执行数据库(或缓存)查询的性能会很差,因为我们试图在每个线程和分钟内添加 1000 个对象。如果我在每次插入后尝试刷新数据库,则会收到以下错误:Deadlock found when trying to get lock;尝试重启事务

所以我的问题是:我怎样才能插入同时具有来自不同线程的唯一约束的对象。

//编辑:目前我正在使用 Hibernate 和 MYSQL InnoDB

//Edit2:最后是我用来写单项的代码。

public class ItemWriterRunnable implements Callable<Object> {

    private final ThreadLocal<Session> session = new ThreadLocal<Session>();

    private Item item;

    public ItemWriterRunnable(Item item) {
        super();
        this.item= item;
    }

    protected Session currentSession() {
        Session s = this.session.get();
        // Open a new Session, if this thread has none yet
        if (s == null || !s.isOpen()) {
            s = HibernateUtils.getSessionFactory().openSession();
            // Store it in the ThreadLocal variable
            this.session.set(s);
        }
        return s;
    }

    @Override
    public Object call() throws Exception {
        Session currentSession = currentSession();
        try {
            currentSession.beginTransaction();
            currentSession.save(this.item);
            currentSession.getTransaction().commit();
        } catch (ConstraintViolationException e) {
            currentSession.getTransaction().rollback();
        } catch (RuntimeException e) {
            currentSession.getTransaction().rollback();
        } finally {
            currentSession.close();
            currentSession = null;
            this.session.remove();
        }
        return null;
    }
}

最好的问候, 安德烈

【问题讨论】:

  • 您可以为每个对象生成一个线程,并在该线程中启动一个事务来写入该对象,而忽略任何由违反约束引起的错误。
  • 这是否也允许多行插入? (例如,批量插入 10 个对象?每个对象一个事务听起来开销很大还是我错了?
  • 不,每个插入一个对象。你别无选择——你不能在没有信号量的情况下进行检查/写入来锁定你的数据库并阻止该锁上的所有线程做任何工作,而且这只适用于单个 jvm——如果你分发你的事务例如,对于一个集群,你搞砸了。如果每次写入有多个对象,如果一个失败,它们都会失败。每次写入一个对象意味着只有一个对象失败。使用 check-then-write 模式时,线程开销没有锁定开销大。
  • 好的,谢谢。我明天试试看;)
  • 最后,我按照您描述的方式实现了它,并发性得到了真正的正确处理。谢谢你。但是,我仍在为性能而苦苦挣扎。我使用产生 25 个线程的 ExecutorService。每个线程都获得一个可调用对象,启动一个会话,启动一个事务,保存项目,提交事务并关闭事务。 (我添加了上面的代码)。有没有办法提高性能?

标签: java multithreading hibernate concurrency


【解决方案1】:

如果您在一个线程中写入多个对象,其中一个因为重复而失败,那么您必须确定哪个是重复的,将其从集合中删除,然后重试将其写入数据库(随着另一个失败的变化)。这需要很多时间。或者,您可以在写入集合之前阅读数据库以查看是否有任何重复项,并在写入之前删除重复项。如果这种读/检查/写模式不包含在同步块中,则它是有缺陷的,因为其他线程可能会在步骤之间写入重复项。修复此问题所需的同步将在每次写入时停止您的服务器,暂停所有现有线程,可能会损害性能。

相反,为每个对象生成一个线程,并在该线程中写入对象(不进行读取/检查)。大多数对象将毫无问题地写入,因为大多数对象都没有重复(假设,但它可能是正确的)。重复的对象将失败并出现异常,此时您可以终止该线程,因为相关工作已经完成。

【讨论】:

  • 这是使用执行器而不是生成新线程、将每个写入作为作业分派并使用池中可用连接数的经典案例。
  • 我同意(并且在对 OP 问题的评论中提到了同样多的内容),但这将是一种优化,并且会使这个答案过于复杂。我的方法是提供一个简单的解决方案,可以根据分析器反馈进行重构。
  • 手动为每个请求生成一个线程从来都不是一个好的解决方案,使用执行器调度单个作业是一个设计问题,而不是优化问题。
  • 你完全错了@chrylis。 Executor 是一个更好的设计,但这并不意味着简单易用的方法不是一个好的解决方案——你听说过进化设计吗?也许重构?你从一个简单的解决方案开始,然后重构一个好的解决方案,让你的测试在你进行时保持工作。简单解决方案的另一个好处是演示基本原理,而无需解释实现相同目标的更复杂方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-18
  • 1970-01-01
  • 2022-07-27
  • 2013-05-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多