English 中文(简体)
使用Hibernate SessionFactory时的多线程问题
原标题:Multithread issues in using Hibernate SessionFactory

Have a table temp .. Code:

CREATE TABLE `temp` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `student_id` bigint(20) unsigned NOT NULL,
  `current` tinyint(1) NOT NULL DEFAULT  1 ,
  `closed_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_index` (`student_id`,`current`,`closed_at`),
  KEY `studentIndex` (`student_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

相应的Java pojo是http://pastebin.com/JHZwubWd。此表具有唯一的约束,因此每个学生只能有一条记录处于活动状态。

2) I have a test code which does try to continually add records for a student ( each time making the older active one as inactive and adding a new active record) and also in a different thread accessing some random ( non-related ) table. Code:

public static void main(String[] args) throws Exception {
        final SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        int runs = 0;
        while(true) {
            Temp testPojo = new Temp();
            testPojo.setStudentId(1L);
            testPojo.setCurrent(true);
            testPojo.setClosedAt(new Date(0));
            add(testPojo, sessionFactory);
            Thread.sleep(1500);

            executorService.submit(new Callable<Object>() {
                @Override
                public Object call() throws Exception {
                    Session session = sessionFactory.openSession();
                    // Some dummy code to print number of users in the system.
                    // Idea is to "touch" the DB/session in this background
                    // thread.
                    System.out.println("No of users: " + session.createCriteria(User.class).list().size());
                    session.close();
                    return null;
                }
            });
            if(runs++ > 100) {
                break;
            }
        }

        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.MINUTES);
    }

private static void add(final Temp testPojo, final SessionFactory sessionFactory) throws Exception {
        Session dbSession = null;
        Transaction transaction = null;
        try {
            dbSession = sessionFactory.openSession();
            transaction = dbSession.beginTransaction();

            // Set all previous state of the student as not current.
            List<Temp> oldActivePojos = (List<Temp>) dbSession.createCriteria(Temp.class)
                    .add(Restrictions.eq("studentId", testPojo.getStudentId())).add(Restrictions.eq("current", true))
                    .list();
            for(final Temp oldActivePojo : oldActivePojos) {
                oldActivePojo.setCurrent(false);
                oldActivePojo.setClosedAt(new Date());

                dbSession.update(oldActivePojo);
                LOG.debug(String.format("  Updated old state as inactive:%s", oldActivePojo));
            }
            if(!oldActivePojos.isEmpty()) {
                dbSession.flush();
            }

            LOG.debug(String.format("  saving state:%s", testPojo));
            dbSession.save(testPojo);
            LOG.debug(String.format("  new state saved:%s", testPojo));

            transaction.commit();

        }catch(Exception exception) {
            LOG.fatal(String.format("Exception in adding state: %s", testPojo), exception);
            transaction.rollback();
        }finally {
            dbSession.close();
        }
    }

在运行代码时,经过几次运行,我得到了一个索引约束异常。之所以会发生这种情况,是因为出于某种奇怪的原因,它没有找到最新的活动记录,而是找到了一些旧的过时活动记录,并试图在保存之前将其标记为非活动记录(尽管DB实际上已经有了新的活动记录)。

请注意,这两个代码共享同一个会话工厂,并且这两个编码在完全不同的表上工作。我的猜测是某些内部缓存状态变脏了。如果我使用两个不同的会话工厂作为前台和后台线程,它工作得很好。

另一件奇怪的事情是,在后台线程中(我在其中打印用户编号),如果我将其封装在事务中(即使这只是一个读取操作),代码运行良好!Sp看起来我需要将所有DB操作(无论读/写)封装在事务中,以便它在多线程环境中工作。

有人能指出这个问题吗?

问题回答

是的,基本上,总是需要事务划分:

Hibernate文档说:

数据库或系统事务边界总是必要的。在数据库事务之外不能与数据库进行通信(这似乎让许多习惯于自动提交模式的开发人员感到困惑)。始终使用清晰的事务边界,即使对于只读操作也是如此。根据您的隔离级别和数据库功能,这可能不是必需的,但如果您总是明确地划分事务,则不会有任何负面影响。

在尝试复制您的设置时,我遇到了一些由于缺乏事务划分而导致的问题(尽管与您的不同)。进一步的调查表明,有时,根据连接池配置,add()与前一个call()在同一个数据库事务中执行。将beginTransaction()/commit()添加到call()中解决了该问题。这种行为可能是您的问题的原因,因为根据事务隔离级别,add()可以处理在事务开始时获取的过时数据库快照,即在前一次call()期间。





相关问题
Silverlight, Updating the UI during processing

I have a simple silverlight multifile upload application, and i want to provide the user with some feedback, right now its only in a test phase and i dont have the webservice. Somehow i cant get the ...

Is reading from an XmlDocument object thread safe?

I was wondering if i could safely read from an XmlDocument object using SelectNodes() and SelectSingleNode() from multiple threads with no problems. MSDN says that they are not guaranteed to be ...

Terminating a thread gracefully not using TerminateThread()

My application creates a thread and that runs in the background all the time. I can only terminate the thread manually, not from within the thread callback function. At the moment I am using ...