Ch12.5-2 : Transactional

[Transactional]

直接翻譯的話,可以說成是交易,而MyBaitsGuice推出之後,也根據GuiceAop,也可以寫出Transactional的效果,而我們這個小節會使用這個技術,完成ACID效果的程式。


[相關程式路徑]

app
 └ controllers
   └ AcidController.java     <--ACID主要controller
 └ test
   └db
     └AbstractFooDAO.java    <-- 抽象類別,主要用來給測試寫入資料庫的DAO類別
      FooDAO.java            <-- extends AbstractFooDAO 之後給AcidController.java使用
      Test_FooLocalDAO.java  <-- 測試程式
      FooServiceImpl.java    <-- 我們交易程式,annotation的Transactional會實作在這隻程式上
 └error
  └MyDaoException.java       <-- 要給Transactional發錯誤時的例外類別
   ErrorMessage.java         <-- 例外發生時,要顯示的文字訊息

[相關程式]

app.error.MyDaoException.java
這隻類別的用途是當我們使用Transactional時,可以去呼叫自定義MyDaoException來達成我們想要的錯誤訊息。不過這個範例,只有單純的extends Exception類別,並沒有特別去處理其它事項。

package error;

public class MyDaoException extends Exception {

  private static final long serialVersionUID = 1L;

  public MyDaoException(String message) {
    super(message);
  }
}

app.error.ErrorMessage.java

我們有自己設計的MyDaoException,那我們也可以自己設計出對應的錯誤訊息類別,去傳遞我們真正需要說明解釋的部份。

package error;

public class ErrorMessage {

  public final static String DAO_ERROR1 = "insert database error";

}

app.test.db.FooServiceImpl.java

這個類別主要受到Annotation Transactional的保護,在signupNewMember方法加上之後,若執行時期發生錯誤時,就可以去做後續處理。其中rethrowExceptionsAs exceptionMessage ,可以引用我們剛剛好寫MyDaoExceptionmEexceptionMessage 的例外訊息內容,丟回給使用者知道,就可以知道發生什麼錯誤了。

package test.db;

import org.apache.ibatis.session.*;
import org.mybatis.guice.transactional.Isolation;
import org.mybatis.guice.transactional.Transactional;

import com.google.inject.Inject;

import error.ErrorMessage;
import error.MyDaoException;
import pojo.web.signup.request.SignupRequest;
import services.WebService;

/**
 * Test Guice and MyBatis Transactional example 
 */
public class FooServiceImpl{

  @Inject
  private WebService webService;

  @Transactional(
      executorType = ExecutorType.BATCH,
      isolation = Isolation.READ_UNCOMMITTED,
      rethrowExceptionsAs = MyDaoException.class,
      exceptionMessage = ErrorMessage.DAO_ERROR1
  )
  public int signupNewMember(SignupRequest signupRequest) throws MyDaoException {
    int num = this.webService.signupNewMember(signupRequest);
    // error test
    num = num / 0;
    return num;
  }


}

app.test.db.AbstractFooDAO.java

在上一個章節我們有新增這個類別,接下來我們要新增這次要使用的方法是testErrorWithAnnotationTransation,會去呼叫到fooServiceImpl來執行寫入資料的動作。

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 error.MyDaoException;
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 void testErrorWithAnnotationTransation(SignupRequest request) throws MyDaoException {
    fooServiceImpl.signupNewMember(request);
  }

}

test.db.FooDAO.java

這個類別是準備給AcidController使用。

package test.db;

import com.google.inject.Singleton;

@Singleton
public class FooDAO extends AbstractFooDAO {


}

test.db.Test_FooLocalDAO.java

我們先寫好case2來去執行我們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 error.HelperException;
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 case2(){
    play.Logger.info("----------------------------------------------");
    play.Logger.info("Case 2 Test Annation Transactional rollback - start");
    SignupRequest request = new SignupRequest();
    try{
      request.setEmail("[email protected]");
      request.setUsername("222");
      request.setPassword("222");
      dao.testErrorWithAnnotationTransation(request);
    } catch (Exception e) {
      e.printStackTrace();
    }
    play.Logger.info("Test Annation Transactional rollback - end");
    play.Logger.info("----------------------------------------------");
  }

  ...  

  @AfterClass
  public static void after(){
    play.Logger.info("Test_FooLocalDAO , test case finish");
  }

}

app.controllers.AcidController.java

測試程式完成之後,我們就可以把這段程式,寫到controller測試,去呼叫我寫的testErrorWithAnnotationTransation,就可以完成了。

package controllers;

import javax.inject.Inject;

import error.HelperException;
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 annotationACID(){
    String tewat = "";
    try{
      SignupRequest request = new SignupRequest();
      request.setEmail("[email protected]");
      request.setUsername("222");
      request.setPassword("222");
      fooDao.testErrorWithAnnotationTransation(request);
    } catch (Exception e) {
      tewat = "cause:" + e.getCause() + ",message:" + e.getMessage();
    } finally {
    }
    return ok("annotationACID rollback Testing " +
              "\n Error info = " + tewat);
  }

  ...

}

conf.routes

完成相關程式之後,最後就是寫下對應的網址與呼叫的controller

# http://127.0.0.1:9000/acid/annotationACID
GET        /acid/annotationACID                    controllers.AcidController.annotationACID()

[Test Case]

Offline CASE1:

我們可以看到測試程式執行時期,發生錯誤,這時候FooServiceImpl裡的Annotation Transactional就可以發揮效果,會去觸發到TransactionalMethodInterceptor,就會丟出ErrorMessage.DAO_ERROR1的自訂錯誤訊息,而無法寫入這筆資料,而跑去執行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 2 Test Annation Transactional rollback - start
java.lang.Exception: insert database error
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at org.mybatis.guice.transactional.TransactionalMethodInterceptor.convertThrowableIfNeeded(TransactionalMethodInterceptor.java:178)
    at org.mybatis.guice.transactional.TransactionalMethodInterceptor.invoke(TransactionalMethodInterceptor.java:103)
    at test.db.AbstractFooDAO.testErrorWithAnnotationTransation(AbstractFooDAO.java:46)
    at test.db.Test_FooLocalDAO.case2(Test_FooLocalDAO.java:52)
    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)
Caused by: java.lang.ArithmeticException: / by zero
    at test.db.FooServiceImpl.signupNewMember(FooServiceImpl.java:31)
    at org.mybatis.guice.transactional.TransactionalMethodInterceptor.invoke(TransactionalMethodInterceptor.java:100)
    ... 27 more
[info] application - Test Annation Transactional rollback - end
[info] application - ----------------------------------------------
[info] application - Test_FooLocalDAO , test case finish

Online CASE1:

實際Play運行之後,其實一樣發揮如測試程式般一樣的結果,並不會寫入資料。

Test URL : http://127.0.0.1:9000/acid/sessionManagerACID

annotationACID rollback Testing 
 Error info = cause:java.lang.ArithmeticException: / by zero,message:insert database error

[Final]

這個小節我們學習到如何去使用MyBaits Transactional保護我們的資料若是有一連串事件要去處理時,又要保證每次資料是正確的話,就很適合使用這個方式,去保護我們資料庫資料的正確性。下一個小節會介紹說明使用FastClasspathScannerException相互配合,達成只印出相關程式錯誤訊息,那我們往下一個小節前進吧!


[Reference]

1.mybatis transactional

http://www.mybatis.org/guice/transactional.html

results matching ""

    No results matching ""