Ch12.5-2 : Transactional
[Transactional]
直接翻譯的話,可以說成是交易,而MyBaits在Guice推出之後,也根據Guice與Aop,也可以寫出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 ,可以引用我們剛剛好寫MyDaoExceptionm與EexceptionMessage 的例外訊息內容,丟回給使用者知道,就可以知道發生什麼錯誤了。
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保護我們的資料。若是有一連串事件要去處理時,又要保證每次資料是正確的話,就很適合使用這個方式,去保護我們資料庫資料的正確性。下一個小節會介紹說明使用FastClasspathScanner與Exception相互配合,達成只印出相關程式錯誤訊息,那我們往下一個小節前進吧!
[Reference]
1.mybatis transactional