我在Jdbi中重现了你的JDBC测试,它的工作原理是:
@Test public void transactionsAreIsolated() { try (Handle h1 = jdbi.open(); Handle h2 = jdbi.open()) { h1.begin(); h2.begin(); assertThat(count(h1)).isEqualTo(0); assertThat(count(h2)).isEqualTo(0); // locks h2's txn to the current snapshot insert(h1, 1, 1); assertThat(count(h1)).isEqualTo(1); assertThat(count(h2)).describedAs("read uncommitted").isEqualTo(0); h1.commit(); assertThat(count(h1)).isEqualTo(1); assertThat(count(h2)).describedAs("read committed").isEqualTo(0); h2.rollback(); } }
从测试开始,在您实际与事务中的数据库进行交互之前,事务似乎并未实际锁定到数据库快照。
在上面的测试中,我们观察了行数 h2 在插入行之前 h1 。此交互设置了事务快照,这就是JDBC测试工作的原因。
h2
h1
但是如果我们修改上面的测试就得出结论了 h1 在观察计数之前的交易 h2 :
@Test public void transactionsLockToStateWhenObserved() { try (Handle h1 = jdbi.open(); Handle h2 = jdbi.open()) { h1.begin(); h2.begin(); insert(h1, 1, 1); assertThat(count(h1)).isEqualTo(1); h1.commit(); assertThat(count(h2)) .describedAs("_now_ we're locked to a snapshot") .isEqualTo(1); h2.rollback(); } }
您的原始测试有两个同步点(事务已启动,事务1已提交),但需要四个才能完全测试您的场景:
@Test public void concurrentTransactionsAreIsolated() throws Exception { CyclicBarrier barrier = new CyclicBarrier(2); Runnable sync = uncheckedRunnable(() -> barrier.await(1, TimeUnit.SECONDS)); jdbi.useTransaction(handle -> insert(handle, 1, 1)); CompletableFuture<Void> first = CompletableFuture.runAsync(uncheckedRunnable(() -> { jdbi.useTransaction(tx -> { assertThat(tx.isInTransaction()).isTrue(); assertThat(tx.getTransactionIsolationLevel()).isEqualTo(TransactionIsolationLevel.SERIALIZABLE); log.info("first tx started"); sync.run(); // wait for both transactions to start insert(tx, 2, 2); log.info("first tx inserted row"); sync.run(); // let the second txn check uncommitted reads sync.run(); // wait for second txn to check the uncommitted reads }); log.info("first tx committed"); sync.run(); // transaction closed, let second transaction check committed reads })); CompletableFuture<Integer> subject = CompletableFuture.supplyAsync(uncheckedSupplier(() -> { int out = jdbi.inTransaction(tx -> { assertThat(tx.isInTransaction()).isTrue(); assertThat(tx.getTransactionIsolationLevel()).isEqualTo(TransactionIsolationLevel.SERIALIZABLE); log.info("second tx started"); sync.run(); // wait for both transactions to start sync.run(); // wait for first txn to insert log.info("second tx checking uncommitted read"); assertThat(count(tx)).isEqualTo(1); sync.run(); // let the first txn commit sync.run(); // wait for first txn to commit log.info("second tx checking committed read"); return count(tx); }); log.info("second tx committed"); return out; })); // capture exceptions from either thread CompletableFuture.allOf(first, subject).get(); assertThat(subject.get()).isEqualTo(1); }