テーブル「一時」を持っています..コード:
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です。このテーブルには、学生ごとに 1 つのレコードしかアクティブにできないという一意の制約があります。
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();
}
}
コードを実行すると、数回実行した後、インデックス制約の例外が発生します。これは、何らかの奇妙な理由で、最新のアクティブ レコードが見つからず、代わりに古い古いアクティブ レコードが見つかり、保存する前に非アクティブとしてマークしようとするために発生します (実際には、DB には新しいアクティブ レコードが既に存在します)。
両方のコードが同じセッション ファクトリを共有し、両方のコードがまったく異なるテーブルで動作することに注意してください。私の推測では、一部の内部キャッシュの状態がダーティになるということです。フォアグラウンド スレッドとバックグラウンド スレッドに 2 つの異なる sessionfactory を使用すると、正常に動作します。
もう 1 つの奇妙な点は、バックグラウンド スレッド (ユーザー数を出力する場所) で、トランザクションでラップすると (読み取り操作のみであっても)、コードが正常に動作することです。Sp は、マルチスレッド環境で動作するように、トランザクションですべての DB 操作 (読み取り/書き込みに関係なく) をラップする必要があるようです。
誰かが問題を指摘できますか?