package org.nutz.trans; import static org.junit.Assert.*; import java.sql.Connection; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.Assert; import org.junit.Test; import org.nutz.Nutzs; import org.nutz.dao.ConnCallback; import org.nutz.dao.test.DaoCase; import org.nutz.service.IdEntityService; /** * @author amosleaf(amosleaf@gmail.com) */ public class TransLevelTest extends DaoCase { private static IdEntityService<Company> comService; static class QueryCompany_ReadCommitted extends DaoCase implements Callable<String> { private int id; QueryCompany_ReadCommitted(int id) { this.id = id; } public String call() throws Exception { ResultAtom<String> ra = null; Trans.exec(Connection.TRANSACTION_READ_COMMITTED, ra = new ResultAtom<String>() { public void run() { setResult(comService.dao().fetch(Company.class, id).getName()); } }); return ra.getResult(); } } /** * Add flag " finished ", we should not wait the thread by fix time, This * flat help to make each test function more faster. * * @author zozoh(zozohtnt@gmail.com) * */ static class RepeatableRead extends DaoCase implements Runnable { RepeatableRead(int id) { this.id = id; } private int id; boolean finished; public void run() { try { Trans.exec(Connection.TRANSACTION_READ_COMMITTED, new Atom() { public void run() { Company c = new Company(); c.setId(id); c.setName("update"); comService.dao().update(c); } }); } catch (Exception e) {} finally { finished = true; } } } static abstract class ResultAtom<T> implements Atom { private T result; public T getResult() { return result; } public void setResult(T result) { this.result = result; } } @Override protected void before() { dao.create(Company.class, true); comService = new IdEntityService<Company>(dao) {}; Company c = Company.create("com1"); comService.dao().insert(c); c = Company.create("com2"); comService.dao().insert(c); c = Company.create("com3"); comService.dao().insert(c); } @Override protected void after() { super.after(); } private static Company duplicate(Company old) { Company c = new Company(); c.setId(old.getId()); c.setName(old.getName()); return c; } @Test public void testTransLevel() { final int[] ls = new int[5]; dao.run(new ConnCallback() { public void invoke(Connection conn) throws Exception { ls[0] = conn.getTransactionIsolation(); } }); Trans.exec(Connection.TRANSACTION_SERIALIZABLE, new Atom() { public void run() { dao.run(new ConnCallback() { public void invoke(Connection conn) throws Exception { ls[1] = conn.getTransactionIsolation(); } }); } }); dao.run(new ConnCallback() { public void invoke(Connection conn) throws Exception { ls[2] = conn.getTransactionIsolation(); } }); Trans.exec(Connection.TRANSACTION_READ_COMMITTED, new Atom() { public void run() { dao.run(new ConnCallback() { public void invoke(Connection conn) throws Exception { ls[3] = conn.getTransactionIsolation(); } }); } }); dao.run(new ConnCallback() { public void invoke(Connection conn) throws Exception { ls[4] = conn.getTransactionIsolation(); } }); assertEquals(Connection.TRANSACTION_SERIALIZABLE, ls[1]); assertEquals(ls[0], ls[2]); assertEquals(Connection.TRANSACTION_READ_COMMITTED, ls[3]); assertEquals(ls[0], ls[4]); } @Test public void testReadCommitted() { // SqlServer/hsql 在这个测试中,两个线程会相互等待 ... if (dao.meta().isSqlServer() || dao.meta().isHsql() || dao.meta().isDerby()) { Nutzs.notSupport(dao.meta()); } // H2 会在抛异常:Timeout trying to lock table "TRANS_COMPANY"; else if (dao.meta().isH2()) { Nutzs.notSupport(dao.meta()); } else { final ExecutorService es = Executors.newCachedThreadPool(); final Company c = dao.fetch(Company.class, dao.getMaxId(Company.class)); Trans.exec(Connection.TRANSACTION_READ_COMMITTED, new Atom() { public void run() { Company c1 = duplicate(c); c1.setName("update"); comService.dao().update(c1); try { String theName = es.submit(new QueryCompany_ReadCommitted(c.getId())).get(); assertEquals(c.getName(), theName); } catch (Exception e) { Assert.assertTrue(false); } c1.setName(c.getName()); comService.dao().update(c1); } }); es.shutdown(); assertEquals(c.getName(), comService.fetch(c.getId()).getName()); } } // cancel the this @Test, in OpenJDK, it will go into an endless looping public void testRepeatableRead() { // SqlServer 在这个测试中,两个线程会相互等待 ... if (dao.meta().isSqlServer()) { Nutzs.notSupport(dao.meta()); } else { final ExecutorService es = Executors.newCachedThreadPool(); final Company c = dao.fetch(Company.class, dao.getMaxId(Company.class)); Trans.exec(Connection.TRANSACTION_READ_COMMITTED, new Atom() { public void run() { assertEquals(c.getName(), comService.fetch(c.getId()).getName()); RepeatableRead task = new RepeatableRead(c.getId()); es.submit(task); // spinning to wait the task thread finish its job while (!task.finished) {} // Then test the result assertEquals("update", comService.fetch(c.getId()).getName()); } }); es.shutdown(); } } @Test public void testSerializable() { final Company c = dao.fetch(Company.class, dao.getMaxId(Company.class)); Trans.exec(Connection.TRANSACTION_SERIALIZABLE, new Atom() { public void run() { Company c1 = duplicate(c); c1.setName("xyz"); dao.update(c1); } }); Company c2 = dao.fetch(Company.class, c.getId()); assertEquals("xyz", c2.getName()); } // cancel the this @Test, in OpenJDK, it will go into an endless looping public void test_serializable_in_2_thread() { // MySql 会导致两个线程互相锁。估计是 InnoDB 只是到表级锁的原因 // 所以,这个测试不测试 MySql if (dao.meta().isMySql()) { Nutzs.notSupport(dao.meta()); } // H2 不支持这个事务级别 else if (dao.meta().isH2()) { Nutzs.notSupport(dao.meta()); } // Oracle, 会导致 java.sql.SQLException: ORA-08177: 无法连续访问此事务处理 else if (dao.meta().isOracle()) { Nutzs.notSupport(dao.meta()); } // SqlServer 在这个测试中,两个线程会相互等待 ... else if (dao.meta().isSqlServer()) { Nutzs.notSupport(dao.meta()); } // 在 Postgresql 下,工作良好 else { final ExecutorService es = Executors.newCachedThreadPool(); final Company c = dao.fetch(Company.class, dao.getMaxId(Company.class)); Trans.exec(Connection.TRANSACTION_SERIALIZABLE, new Atom() { public void run() { assertEquals(c.getName(), comService.fetch(c.getId()).getName()); RepeatableRead task = new RepeatableRead(c.getId()); es.submit(task); // spinning to wait the task thread finish its job while (!task.finished) {} // Then test the result, The data didn't change assertEquals(c.getName(), comService.fetch(c.getId()).getName()); } }); // Output of the trans, fetch again, the data changed Company c2 = dao.fetch(Company.class, c.getId()); assertEquals("update", c2.getName()); } } }