4

テーブル「一時」を持っています..コード:

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 操作 (読み取り/書き込みに関係なく) をラップする必要があるようです。

誰かが問題を指摘できますか?

4

1 に答える 1

0

はい、基本的に、トランザクション境界は常に必要です。

Hibernate のドキュメントには次のように書かれています。

データベース、またはシステム、トランザクションの境界は常に必要です。データベースとの通信は、データベース トランザクションの外では発生しません (これは、自動コミット モードに慣れている多くの開発者を混乱させるようです)。読み取り専用操作であっても、常に明確なトランザクション境界を使用してください。分離レベルとデータベースの機能によっては、これが必要ない場合もありますが、トランザクションを常に明示的に区別する場合、マイナス面はありません。

セットアップを再現しようとしたときに、トランザクション境界の欠如によって引き起こされたいくつかの問題が発生しました (ただし、あなたのものとは異なります)。さらに調査したところ、接続プールadd()の構成によっては、以前のcall(). beginTransaction()/commit()を追加するとcall()、その問題が修正されました。この動作が問題の原因である可能性があります。トランザクションの分離レベルによっては、add()トランザクションの開始時 (つまり、前のcall().

于 2011-02-13T15:24:02.497 に答える