Ch12.5-1 : SqlSessionManager

[SqlSessionManager]

這個小節會先示範沒有使用MyBatisAnnotation 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變成falsebatch模式是可以重複使用的預處理的語句,可以加快資料寫入的速度。而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效果。而我們也可以使用MyBatisGuice搭配,一樣可以達成相關的效果。而這個小節我們是用比較早期的寫法,自己去呼叫SqlSessionManager來去達成,若是比較期望自行控制呼叫commitrollback的時機的話,這個小節會是很好的範例。

下一個小節我們會使用MyBaits使用GuiceAOP達成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/

results matching ""

    No results matching ""