【问题标题】:Synchronizing Hibernate inserts using guarded block使用保护块同步 Hibernate 插入
【发布时间】:2016-11-03 14:15:58
【问题描述】:

我正在尝试通过 MySQL 中的 Hibernate 解决并发数据库插入冲突的问题。

我有一段代码可以很容易地被多个线程同时执行。它正在检查数据库中是否存在记录,如果不存在,则插入新记录。在相关的子记录上执行相同的如果不存在则插入操作。如果两个线程尝试同时保留子记录,我会得到一个 ConstraintViolationException,因为两个线程在查询时都看到该记录不存在,因此两个线程都尝试保存违反唯一约束的同一记录,其中一个失败了。

我正在尝试使用受保护的块在应用程序级别同步查询插入操作,以便一个线程在查询数据库之前等待另一个线程完成插入记录。但即使我看到同步工作,查询记录仍然没有返回任何结果,即使该记录已被持久保存在另一个线程中。所以约束冲突仍然发生。

  • 我使用的是 Hibernate 5.1.0
  • 我正在手动管理数据库事务
  • 我已全局启用查询缓存和二级缓存,但我将 CacheMode.REFRESH 用于 SELECT 查询
  • 我没有使用乐观或悲观的数据库锁定或行版本控制。

这是一个代码示例:

在每个同步操作中,如果 Product 不存在,我会尝试保留它,如果它不存在,我会尝试保留相关的父供应商。

public class UpdateProcessor extends HttpServlet {

  // Indicator used for synchronizing read-insert operations
  public static Boolean newInsertInProgress = false;

  @Override
  public void doPost(HttpServletRequest request, HttpServletResponse response) {

    Session hbSession = null;
    Transaction tx = null;
    try {
      hbSession = HibernateUtils.getNewSession();

      UpdateProcessor.waitForInsert(); // if there is an insert in progress, wait for it to finish
      UpdateProcessor.notifyInsertStarted(); // obtain lock

      tx = hbSession.beginTransaction();

      Product existingProduct = findProductBySKU(sku);
      if(existingProduct == null) {

        Product newProduct = new Product();
        newProduct.setSKU(sku);

        Supplier existingSupplier = findSupplierByName(name);
        if(existingSupplier == null) {
          Supplier newSupplier = new Supplier();
          newSupplier.setName(name);
          db.save(newSupplier);
          newProduct.setSupplier(newSupplier);
        } else {
          newProduct.setSupplier(existingSupplier);
        }

        db.save(newProduct);
      }

      tx.commit();

    } catch (Exception t) {
      // <rollback transaction>
      response.sendError(500);
    } finally {

      // Safeguard to avoid thread deadlock - release lock always, if obtained
      if(UpdateProcessor.newInsertInProgress) {
            UpdateProcessor.notifyInsertFinished(); // release lock and notify next thread
      }

      // <close session>
    }
  }

  private static synchronized void waitForInsert() {
    if(!UpdateProcessor.newInsertInProgress) {
        log("Skipping wait - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
        return;
    }
    while(UpdateProcessor.newInsertInProgress) {
        boolean loggedEntering = false;
        if(!loggedEntering) {
            log("Entering wait - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
            loggedEntering = true;
        }
        try {
            UpdateProcessor.class.wait();
        } catch (InterruptedException e) {}
    }
    log("Exiting wait - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
  }

  private static synchronized void notifyInsertStarted() {
    UpdateProcessor.newInsertInProgress = true;
    UpdateProcessor.class.notify();
    log("Notify start - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
  }

  private static synchronized void notifyInsertFinished() {
    UpdateProcessor.newInsertInProgress = false;
    UpdateProcessor.class.notify();
    log("Notify finish - thread " + Thread.currentThread().getId() + " - " + System.currentTimeMillis());
  }
}

并发请求后的输出:

Skipping wait - thread 254 - 1478171162713
Notify start - thread 254 - 1478171162713
Entering wait - thread 255 - 1478171162713
Entering wait - thread 256 - 1478171162849
Notify finish - thread 254 - 1478171163050
Exiting wait - thread 255 - 1478171163051
Notify start - thread 255 - 1478171163051
Entering wait - thread 256 - 1478171163051
Error - thread 255:
org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '532-supplier-name-1' for key 'supplier_name_uniq'

保持新的供应商记录仍然在线程 255 中引发异常,因为违反了唯一约束(id、名称)。

为什么 SELECT 在同步插入后仍然没有返回任何记录?保护锁是避免多插入问题的正确方法吗?

【问题讨论】:

  • 您是否尝试过在保存后刷新 Hibernate 会话? hSession.flush()
  • 是的,即使手动刷新它仍然会引发异常。事务的提交 (tx.commit()) 也在幕后刷新。
  • 为了补全,能不能把你的缓存,所有的缓存都关掉,试试看?
  • 我想检查的最后一件事是如果你在 UpdateProcessor.notifyInsertStarted(); 之后移动获取会话会发生什么; // 获取锁
  • 你应该可以在外面关闭,是的。很高兴它成功了!

标签: java mysql multithreading hibernate concurrency


【解决方案1】:

根据上面 Mechkov 的回答:

简短回答:我需要在同步的代码中包含 Hibernate 会话创建。

长答案: 受保护的块正确地同步了查询插入块,但问题是即使一个线程完成了记录的持久化,第二个线程在创建新的 Hibernate 会话之前无法看到数据库中的更改。所以并发数据库修改的效果并不是对所有线程都立即可见的。通过在其他线程中进行插入之后创建会话来获得最新的数据库状态。在同步代码中包含会话创建确保了这种情况。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-30
    • 1970-01-01
    • 2017-09-24
    • 1970-01-01
    • 1970-01-01
    • 2021-12-05
    • 2016-03-02
    • 1970-01-01
    相关资源
    最近更新 更多