简体   繁体   English

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

[英]Java: Running DAO class in multithreaded and transactional environment

I am calling a DAO class from multithreaded environment that fires a select query to get a row ( get a ticketId) from the table and then update the same row (with a customerId). 我正在从多线程环境中调用DAO类,该类将触发选择查询以从表中获取行(获取ticketId),然后更新同一行(带有customerId)。 This happens in the same transaction. 这在同一事务中发生。 My database is SQL Server. 我的数据库是SQL Server。 When I fire the select query I try to put a row level lock (WITH ROWLOCK) so that the other threads don't get the same row. 当我触发选择查询时,我尝试放置一个行级别的锁(WITH ROWLOCK),以便其他线程不会得到同一行。 My DAO class is as follows (Only the important piece of code is shown here): 我的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();
        }
    }
}

I spawn 4 threads. 我产生了4个线程。 What I see in the log file is that all the four threads get to the same record in the database table. 我在日志文件中看到的是,所有四个线程都到达了数据库表中的同一条记录。

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

First of all this shouldn't happen, correct? 首先,这不应该发生,对吗? I am expecting to see that each thread should get different row. 我期望看到每个线程应该获得不同的行。

Then I see that only one thread is able to successfully update the database. 然后,我看到只有一个线程能够成功更新数据库。

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

The other three thread dies at the line: 该行的其他三个线程死亡:

int result = query2.executeUpdate();

I don't understand what happens to the other three threads as I don't see anything in my log file. 我不了解其他三个线程的情况,因为我在日志文件中看不到任何内容。

Someone please help me here. 有人请在这里帮助我。

Thanks Raj 谢谢拉吉

It's unclear (at least to me), exactly what error Sql Server and hibernate are returning in the case of the 3 failed tx's. 目前尚不清楚(至少对我来说),在3个失败的TX的情况下,究竟Sql Server和hibernate会返回什么错误。 But it's not surprising that they failed. 但是他们失败了也就不足为奇了。

(Row) Locks are not queues, and nor are they filters. (行)锁不是队列,也不是过滤器。 It's not surprising that the four select queries will return the same row because the presence of the lock does not change the data, and therefore also doesn't change the query results - it merely guards access to the data. 四个选择查询将返回同一行也就不足为奇了,因为锁的存在不会更改数据,因此也不会更改查询结果-它只是保护对数据的访问 I suspect, but do not know, that the lock might be bypassed here by hibernate caching the query. 我怀疑但不知道,通过休眠缓存查询可以绕过锁定。

The root problem is that you have four processes all contending for a single resource (the first unassigned ticket). 根本问题是,您有四个进程争用一个资源(第一个未分配的票证)。 While a properly implemented row locking scheme would work, it's a poor choice because it doesn't scale as well as the alternatives. 尽管正确实施的行锁定方案可以工作,但它是一个糟糕的选择,因为它的伸缩性不及其他方案。

You'd be better off writing your dao with a synchronization block. 您最好使用同步块来编写dao。 This will handle the case where multiple threads in the appserver are contending for the resource at the same time. 这将处理appserver中的多个线程同时争用资源的情况。 The simplest way would be to do this: 最简单的方法是:

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

This handles the single appserver case nicely, although you need to be aware that there's no guarantee as to the order in which the threads are executed. 尽管您需要知道并不能保证线程的执行顺序,但这可以很好地处理单个appserver的情况。 I suspect, based on your variable names, that that might be a problem for you, but even if so, this solution doesn't make the problem any worse. 根据您的变量名,我怀疑这可能对您来说是个问题,但是即使这样,此解决方案也不会使问题变得更糟。

In the case where you have multiple appservers, then you can still have multiple processes contending for the same resource. 如果您有多个应用服务器,那么仍然可以有多个进程争用同一资源。 Again, this could be solved with a (pessimistic) row lock, but I suspect you'd be better off with an optimistic locking solution. 同样,这可以通过(悲观的)行锁来解决,但是我怀疑使用乐观的锁解决方案会更好。 The optimistic lock would look something like this: 乐观锁如下所示:

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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM