有一张桌子'临时'.. 代码:
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)我有一个测试代码,它试图不断地为学生添加记录(每次将旧的活动记录作为非活动状态并添加新的活动记录),并且还在不同的线程中访问一些随机的(非相关的) )表。 代码:
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();
}
}
运行代码后,经过几次运行后,我得到一个索引约束异常。之所以会发生这种情况,是因为出于某些奇怪的原因,它找不到最新的活动记录,而是找到一些较旧的过时活动记录,并尝试在保存之前将其标记为非活动状态(尽管数据库实际上已经存在新的活动记录)。
请注意,两个代码共享相同的sessionfactory,并且这两个代码在完全不同的表上工作。我的猜测是一些内部缓存状态变脏了。如果我为前台和后台线程使用2个不同的sessionfactory,它可以正常工作。
另一个奇怪的事情是在后台线程(我打印用户号),如果我将它包装在一个事务中(即使它只是一个读操作),代码工作正常! Sp看起来我需要在事务中包装所有数据库操作(无论读/写),以便在多线程环境中工作。
有人可以指出这个问题吗?
答案 0 :(得分:0)
是的,基本上,总是需要事务划分:
数据库或系统,事务边界始终是必需的。在数据库事务之外不会发生与数据库的通信(这似乎使许多习惯于自动提交模式的开发人员感到困惑)。始终使用明确的事务边界,即使对于只读操作也是如此。根据您的隔离级别和数据库功能,这可能不是必需的,但如果您始终明确划分事务,则没有任何缺点。
当尝试重现您的设置时,我遇到了由于缺少事务划分而导致的一些问题(尽管与您的不一样)。进一步的调查表明,有时,根据连接池配置,add()
在与前一个call()
相同的数据库事务中执行。将beginTransaction()
/ commit()
添加到call()
修复了该问题。此行为可能导致您的问题,因为,根据事务隔离级别,add()
可以使用在事务开始时(即在上一个call()
期间)获取的数据库的陈旧快照。