Есть таблица 'temp'. Код:
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();
}
}
При выполнении кода после нескольких запусков я получаю исключение ограничения индекса.Это происходит потому, что по какой-то странной причине он не находит последнюю активную запись, а вместо этого устаревшую устаревшую активную запись и пытается пометить ее как неактивную перед сохранением (хотя БД фактически уже имеет новую активную запись).* Обратите внимание, что оба кода используют одну и ту же сессионную фабрику, и оба кода работают с совершенно разными таблицами.Я предполагаю, что некоторое внутреннее состояние кэша становится грязным.Если я использую 2 разные сессионные фабрики для переднего плана и фонового потока, он работает нормально.
Еще одна странная вещь заключается в том, что в фоновом потоке (где я печатаю номер пользователя), если я заключаю его в транзакцию (даже если это только операция чтения), код работает нормально!Sp выглядит так, как будто мне нужно обернуть все операции с БД (независимо от чтения / записи) в транзакцию, чтобы она работала в многопоточной среде.
Может кто-нибудь указать на проблему?