Java:在多线程和事务环境中运行DAO类

时间:2014-02-27 03:51:44

标签: java sql-server multithreading hibernate dao

我从多线程环境调用DAO类,它触发select查询从表中获取一行(获取ticketId),然后更新同一行(使用customerId)。这发生在同一个交易中。我的数据库是SQL Server。当我触发select查询时,我尝试将行级别锁定(WITH ROWLOCK),以便其他线程不会获得相同的行。我的DAO类如下(这里只显示重要的代码段):

public void saveCustomerTicketUsingJDBC(String customerId) {
  Session session = getSession();
  //Session session = SessionFactoryUtils.getSession(getSessionFactory(), true);// Have tried this too
  try {
         session.getTransaction().begin();



        Query query1 = session.createSQLQuery("select TOP 1 * from CustomerTicket WITH (ROWLOCK) where customerId is null");
        Object[] customerTicket = (Object[])query1.uniqueResult();

             Integer id = (Integer)customerTicket[0];
             ticketId = (String)customerTicket[1];
             logger.debug("Got ticket id -->"+ticketId);



        Query query2 = session.createSQLQuery("update CustomerTicket " +
                                                "set customerId = :customerId " +
                                                "where ticketId = :ticketId");
        query2.setParameter("customerId", customerId);
        query2.setParameter("ticketId", ticketId);
        logger.debug("QUery 2 executeUpdate : customerId : "+customerId+", ticketId :"+ticketId);
        int result = query2.executeUpdate();
        logger.debug("result >"+result +", customerTicketId ----------->"+customerId+", ticketId ------------>"+ticketId);
        //session.flush();
        //session.clear();
        session.getTransaction().commit();
  } catch (Exception e) {
        logger.error("Exception while saving customer ticket-->"+e,e);
    } finally {
        if (session != null) {
            session.close();
        }
    }
}

我产生了4个线程。我在日志文件中看到的是,所有四个线程都到达数据库表中的相同记录。

2014-02-26 22:41:29.183 DEBUG [pool-3-thread-2] CustomerTicketDAO.java:83 Got ticket id -->4
2014-02-26 22:41:29.183 DEBUG [pool-3-thread-4] CustomerTicketDAO.java:83 Got ticket id -->4
2014-02-26 22:41:29.184 DEBUG [pool-3-thread-3] CustomerTicketDAO.java:83 Got ticket id -->4
2014-02-26 22:41:29.184 DEBUG [pool-3-thread-1] CustomerTicketDAO.java:83 Got ticket id -->4

首先,这不应该发生,对吗?我期待看到每个线程应该得到不同的行。

然后我看到只有一个线程能够成功更新数据库。

2014-02-26 22:41:29.408 DEBUG [pool-3-thread-1] CustomerTicketDAO.java:93 result >1, customerTicketId ----------->CustomerId_0, ticketId ------------>4

另外三个线程在线上死掉:

int result = query2.executeUpdate();

我不明白其他三个线程会发生什么,因为我在日志文件中没有看到任何内容。

有人请帮助我。

由于 拉吉

1 个答案:

答案 0 :(得分:3)

目前还不清楚(至少在我看来),在3个失败的tx的情况下,究竟是什么错误Sql Server和hibernate返回。但是他们失败并不奇怪。

(行)锁不是队列,也不是过滤器。四个选择查询将返回同一行并不奇怪,因为锁的存在不会更改数据,因此也不会更改查询结果 - 它只是保护访问数据。我怀疑,但不知道,这可能是通过hibernate缓存查询来绕过锁。

根本问题是您有四个进程都争用单个资源(第一个未分配的票证)。虽然正确实施的行锁定方案可行,但它的选择较差,因为它的扩展性不如替代方案。

使用同步块编写dao会更好。这将处理appserver中的多个线程同时竞争资源的情况。最简单的方法是:

public synchronized void saveCustomerTicketUsingJDBC(String customerId) {
    Session session = getSession();
    ...
}

这很好地处理了单个appserver的情况,尽管你需要知道不能保证线程的执行顺序。我怀疑,根据您的变量名称,这对您来说可能是一个问题,但即使如此,此解决方案也不会使问题更严重。

如果您有多个应用程序服务器,那么您仍然可以有多个进程争用同一资源。同样,这可以用(悲观的)行锁解决,但我怀疑你的乐观锁定解决方案会更好。乐观锁看起来像这样:

while (true) {
  session.getTransaction().begin();

  try {  
    Query query1 = session.createSQLQuery("select TOP 1 * from CustomerTicket where customerId is null");
    Object[] customerTicket = (Object[])query1.uniqueResult();

    Integer id = (Integer)customerTicket[0];
    ticketId = (String)customerTicket[1];
    logger.debug("Got ticket id -->"+ticketId);

    Query query2 = session.createSQLQuery("update CustomerTicket " +
                                            "set customerId = :customerId " +
                                            "where ticketId = :ticketId AND customerId is NULL");  
                                            // Notice the AND clause!!!
    query2.setParameter("customerId", customerId);
    query2.setParameter("ticketId", ticketId);
    logger.debug("QUery 2 executeUpdate : customerId : "+customerId+", ticketId :"+ticketId);
    int updateCount = query2.executeUpdate();
    logger.debug("updateCount >"+updateCount +", customerTicketId ----------->"+customerId+", ticketId ------------>"+ticketId);

    //  Did someone beat us to it? 
    if (updateCount == 0) {
        session.getTransaction().rollback();
        continue;
    }

    // Nope - we're winning so far, but the race isn't over yet...
    session.getTransaction().commit();
  } catch (OptimisticLockException ex) {
      logger.debug("Darn, someone DID beat us to it");
      session.getTransaction().rollback();
      continue;
  } catch (Exception ex) {
      ...
  }

  break;
}