Ch12.5-1 : SqlSessionManager
[SqlSessionManager]
這個小節會先示範沒有使用MyBatis的Annotation Transaction時,該怎樣去實作出有ACID效果的交易程式。
[相關程式路徑]
build.sbt
app
└ controllers
└ AcidController.java <--ACID主要controller
└ test
└ db
└ AbstractFooDAO.java <-- 抽象類別,主要用來給測試寫入資料庫的DAO類別
FooDAO.java <-- extends AbstractFooDAO 之後給AcidController.java使用
Test_FooLocalDAO.java <-- 測試程式
[相關程式]
build.sbt
因為我們有測試程式,需要去引用JUnit的jar檔,讓我們專案可以寫測試程式。
libraryDependencies ++= Seq(
...
// Test
"junit" % "junit" % "4.12"
...
)
app.test.db.AbstractFooDAO.java
首先我們新增一個DAO去使用到我們之前寫好的WebService,唯一不同的部份是,我們需要去呼叫到MyBaits內建使用到的SqlSessionManager,在程式開始執行寫入DB前,會使用startManagedSession方法,會先把autocommit變成false,batch模式是可以重複使用的預處理的語句,可以加快資料寫入的速度。而TransactionIsolationLevel.READ_UNCOMMITTED,在尚未commit之前,這期間任何寫入或更新的相關資料不能被其它事務去使用,如果發生錯誤時,將會執行rollback方式,恢復上次的狀態。若是確認資料無誤的話,我們才會執行commit動作把資料寫入或更新進去,最後也需要執行close來關閉連線。而我們測試程式,為了觸發ACID的效果,我們故意把 1/0 錯誤程式,埋在程式當中,當執行到這段時,會發生錯誤,而導致該筆資料沒有寫入,發揮了ACID的效果。
package test.db;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionManager;
import org.apache.ibatis.session.TransactionIsolationLevel;
import com.google.inject.Inject;
import pojo.web.signup.request.SignupRequest;
import services.WebService;
public abstract class AbstractFooDAO {
@Inject
public SqlSessionManager sqlSessionManager;
@Inject
public WebService webService;
@Inject
public FooServiceImpl fooServiceImpl;
public String testErrorWithSessionManager(SignupRequest request) throws ArithmeticException{
sqlSessionManager.startManagedSession(
ExecutorType.BATCH,
TransactionIsolationLevel.READ_UNCOMMITTED);
String errorMessage = "";
try {
webService.signupNewMember(request);
// error test
int i = 1 / 0 ;
sqlSessionManager.commit();
} catch (Exception e) {
e.printStackTrace();
errorMessage = "cause:" + e.getCause() + ",message:" + e.getMessage();
sqlSessionManager.rollback();
}finally {
sqlSessionManager.close();
}
return errorMessage;
}
...
}
test.db.FooDAO.java
這個類別是準備給AcidController使用,而且只要單純的extends AbstractFooDAO即可。
package test.db;
import com.google.inject.Singleton;
@Singleton
public class FooDAO extends AbstractFooDAO {
}
test.db.Test_FooLocalDAO.java
測試程式,是程式上線前,需要驗證程式邏輯是否真的依照我們所想的去執行,首先我們先寫好case1來去執行我們AbstractFooDAO.testErrorWithSessionManager的方法,是否能真的順利完成ACID的效果。
package test.db;
import org.apache.ibatis.session.SqlSessionManager;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
import pojo.web.signup.request.SignupRequest;
import services.WebService;
public class Test_FooLocalDAO extends AbstractFooDAO{
static Test_FooLocalDAO dao;
@BeforeClass
public static void before(){
play.Logger.info("Test_FooLocalDAO , test case start");
Injector injector = Guice.createInjector(new modules.MyBatisModule());
dao = new Test_FooLocalDAO();
dao.sqlSessionManager = injector.getInstance(SqlSessionManager.class);
dao.webService = injector.getInstance(WebService.class);
dao.fooServiceImpl = injector.getInstance(FooServiceImpl.class);
}
@Test
public void case1(){
play.Logger.info("----------------------------------------------");
play.Logger.info("Case 1 Test SessionManager rollback - start");
SignupRequest request = new SignupRequest();
request.setEmail("[email protected]");
request.setUsername("111");
request.setPassword("111");
dao.testErrorWithSessionManager(request);
play.Logger.info("Test SessionManager rollback - end");
play.Logger.info("----------------------------------------------");
}
@AfterClass
public static void after(){
play.Logger.info("Test_FooLocalDAO , test case finish");
}
}
app.controllers.AcidController.java
測試程式完成之後,我們就可以把這段程式,寫到controller測試,特別注意到我改用Inject方式去注入FooDAO 去呼叫我寫的testErrorWithSessionManager,就可以完成了。
package controllers;
import javax.inject.Inject;
import play.mvc.Controller;
import play.mvc.Result;
import pojo.web.signup.request.SignupRequest;
import test.db.FooDAO;
/**
* ACID testing
*
* TEST CASE : @link {Test_FooLocalDAO}
*/
public class AcidController extends Controller {
@Inject
FooDAO fooDao ;
public Result sessionManagerACID(){
SignupRequest request = new SignupRequest();
request.setEmail("[email protected]");
request.setUsername("111");
request.setPassword("111");
String tewsm = fooDao.testErrorWithSessionManager(request);
return ok("sessionManagerACID rollback Testing " +
"\n Error info = " + tewsm );
}
}
conf.routes
完成相關程式之後,最後就是寫下對應的網址與呼叫的controller
# http://127.0.0.1:9000/acid/sessionManagerACID
GET /acid/sessionManagerACID controllers.AcidController.sessionManagerACID()
[Test Case]
Offline CASE1:
我們可以看到測試程式執行到1/0的時候,發生錯誤,這時候AbstractFooDAO裡的sqlSessionManager就發揮效果,而無法寫入這筆資料,而跑去執行rollback的效果,而確保資料的正確性。
[info] application - Test_FooLocalDAO , test case start
[warn] application - hardcoded value: user is deprecated, use username instead
[info] application - Creating Pool for datasource 'play'
[info] application - ----------------------------------------------
[info] application - Case 1 Test SessionManager rollback - start
java.lang.ArithmeticException: / by zero
at test.db.AbstractFooDAO.testErrorWithSessionManager(AbstractFooDAO.java:32)
at test.db.Test_FooLocalDAO.case1(Test_FooLocalDAO.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
[info] application - Test SessionManager rollback - end
[info] application - ----------------------------------------------
[info] application - Test_FooLocalDAO , test case finish
Online CASE1:
實際Play運行之後,其實一樣發揮如測試程式般一樣的結果,並不會寫入資料。
Test URL : http://127.0.0.1:9000/acid/sessionManagerACID
sessionManagerACID rollback Testing
Error info = cause:null,message:/ by zero
[Final]
為什要學習這個章節是因為,早期MyBatis都是與Spring Framework搭配達成ACID效果。而我們也可以使用MyBatis與Guice搭配,一樣可以達成相關的效果。而這個小節我們是用比較早期的寫法,自己去呼叫SqlSessionManager來去達成,若是比較期望自行控制呼叫commit與rollback的時機的話,這個小節會是很好的範例。
下一個小節我們會使用MyBaits使用Guice與AOP達成ACID效果,讓我們繼續往下學習下去吧!
[Reference]
1.關於mybatis的batch模式性能測試及結論
http://www.blogjava.net/diggbag/articles/mybatis.html
2.mybatis transactional
http://www.mybatis.org/guice/transactional.html
3.mybatis jdbc-helper
http://www.mybatis.org/guice/jdbc-helper.html
4.《深入理解mybatis原理》 MyBatis事務管理機制
http://blog.csdn.net/luanlouis/article/details/37992171
5.Database Transaction第一話: ACID
http://karenten10-blog.logdown.com/posts/192629-database-transaction-1-acid
6.Create A Database Transaction Using MyBatis
http://edwin.baculsoft.com/2012/06/create-a-database-transaction-using-mybatis/