前回のSpring3 + MyBatis3のSpringをGuiceに置き換えてみます。
User.java、UserDao.java、UserDao.xml、UserService.javaは前回と同じ物をそのまま利用します。
Guice-MyBatisプラグインの標準のトランザクションインターセプターではreadOnlyの設定ができない(rollbackOnlyは可能)ため、
readOnlyに対応したトランザクションインターセプターを自作します。
ソースコードはGuice-MyBatisプラグインの標準のトランザクションインターセプターを参考にしています。
※PooledDataSourceProviderではなく、BasicDataSourceProviderを使用すればdefaultReadOnlyの設定が可能でした。
ただし、その場合には標準のトランザクションインターセプターではreadOnlyをfalseに設定しなおす事がないようで、更新系の処理ができません。
- TransactionalMethodInterceptor.java
-
- package com.brightgenerous.extension.ibatis;
- import java.lang.reflect.Constructor;
- import java.lang.reflect.Method;
- import java.util.Arrays;
- import javax.inject.Inject;
- import com.brightgenerous.extension.logging.Log;
- import com.brightgenerous.extension.logging.LogFactory;
- import org.aopalliance.intercept.MethodInterceptor;
- import org.aopalliance.intercept.MethodInvocation;
- import org.apache.ibatis.session.ExecutorType;
- import org.apache.ibatis.session.SqlSessionManager;
- import org.apache.ibatis.session.TransactionIsolationLevel;
- public final class TransactionalMethodInterceptor implements MethodInterceptor {
- private static final Class<?>[] CAUSE_TYPES = new Class[] { Throwable.class };
- private static final Class<?>[] MESSAGE_CAUSE_TYPES = new Class[] {
- String.class, Throwable.class };
- private static final Log log = LogFactory
- .getLog(TransactionalMethodInterceptor.class);
- @Inject
- private SqlSessionManager sqlSessionManager;
- private final boolean readOnly;
- private final ExecutorType executorType;
- private final TransactionIsolationLevel isolationLevel;
- private final boolean rollbackOnly;
- private final boolean force;
- private final Class<? extends Throwable> rethrowExceptionsAs;
- private final String exceptionMessage;
- public TransactionalMethodInterceptor() {
- this(false);
- }
- public TransactionalMethodInterceptor(boolean readOnly) {
- this(readOnly, ExecutorType.SIMPLE, null, false, false,
- Exception.class, "");
- }
- public TransactionalMethodInterceptor(boolean readOnly,
- ExecutorType executorType,
- TransactionIsolationLevel isolationLevel, boolean rollbackOnly,
- boolean force, Class<? extends Throwable> rethrowExceptionsAs,
- String exceptionMessage) {
- this.readOnly = readOnly;
- this.executorType = executorType;
- this.isolationLevel = isolationLevel;
- this.rollbackOnly = rollbackOnly;
- this.force = force;
- this.rethrowExceptionsAs = rethrowExceptionsAs;
- this.exceptionMessage = exceptionMessage;
- }
- public void setSqlSessionManager(SqlSessionManager sqlSessionManager) {
- this.sqlSessionManager = sqlSessionManager;
- }
- @Override
- public Object invoke(MethodInvocation invocation) throws Throwable {
- Method interceptedMethod = invocation.getMethod();
- String debugPrefix = null;
- if (log.isDebugEnabled()) {
- debugPrefix = String.format("[Intercepted method: %s]",
- interceptedMethod.toGenericString());
- }
- boolean isSessionInherited = sqlSessionManager
- .isManagedSessionStarted();
- if (isSessionInherited) {
- if (log.isDebugEnabled()) {
- log.debug(String.format(
- "%s - SqlSession already set for thread: %s",
- debugPrefix, Thread.currentThread().getId()));
- }
- } else {
- if (log.isDebugEnabled()) {
- log.debug(String
- .format("%s - SqlSession not set for thread: %s, creating a new one",
- debugPrefix, Thread.currentThread().getId()));
- }
- sqlSessionManager.startManagedSession(executorType, isolationLevel);
- sqlSessionManager.getConnection().setReadOnly(readOnly);
- }
- Object object = null;
- try {
- object = invocation.proceed();
- if (!isSessionInherited && !rollbackOnly) {
- sqlSessionManager.commit(force);
- }
- } catch (Throwable t) {
- sqlSessionManager.rollback(force);
- for (Class<?> exceptionClass : interceptedMethod
- .getExceptionTypes()) {
- if (exceptionClass.isAssignableFrom(t.getClass())) {
- throw t;
- }
- }
- if (rethrowExceptionsAs.isAssignableFrom(t.getClass())) {
- throw t;
- }
- String errorMessage;
- Object[] initargs;
- Class<?>[] initargsType;
- if (exceptionMessage.length() != 0) {
- errorMessage = String.format(exceptionMessage,
- invocation.getArguments());
- initargs = new Object[] { errorMessage, t };
- initargsType = MESSAGE_CAUSE_TYPES;
- } else {
- initargs = new Object[] { t };
- initargsType = CAUSE_TYPES;
- }
- Constructor<? extends Throwable> exceptionConstructor = getMatchingConstructor(
- rethrowExceptionsAs, initargsType);
- Throwable rethrowEx = null;
- if (exceptionConstructor != null) {
- try {
- rethrowEx = exceptionConstructor.newInstance(initargs);
- } catch (Exception e) {
- errorMessage = String
- .format("Impossible to re-throw '%s', it needs the constructor with %s argument(s).",
- rethrowExceptionsAs.getName(),
- Arrays.toString(initargsType));
- log.error(errorMessage, e);
- rethrowEx = new RuntimeException(errorMessage, e);
- }
- } else {
- errorMessage = String
- .format("Impossible to re-throw '%s', it needs the constructor with %s or %s argument(s).",
- rethrowExceptionsAs.getName(),
- Arrays.toString(CAUSE_TYPES),
- Arrays.toString(MESSAGE_CAUSE_TYPES));
- log.error(errorMessage);
- rethrowEx = new RuntimeException(errorMessage);
- }
- throw rethrowEx;
- } finally {
- if (!isSessionInherited) {
- if (rollbackOnly) {
- if (log.isDebugEnabled()) {
- log.debug(debugPrefix + " - SqlSession of thread: "
- + Thread.currentThread().getId()
- + " was in rollbackOnly mode, rolling it back");
- }
- sqlSessionManager.rollback(true);
- }
- if (log.isDebugEnabled()) {
- log.debug(String
- .format("%s - SqlSession of thread: %s terminated its life-cycle, closing it",
- debugPrefix, Thread.currentThread().getId()));
- }
- sqlSessionManager.close();
- } else if (log.isDebugEnabled()) {
- log.debug(String
- .format("%s - SqlSession of thread: %s is inherited, skipped close operation",
- debugPrefix, Thread.currentThread().getId()));
- }
- }
- return object;
- }
- private static <E extends Throwable> Constructor<E> getMatchingConstructor(
- Class<E> type, Class<?>[] argumentsType) {
- Class<? super E> currentType = type;
- while (Object.class != currentType) {
- for (Constructor<?> constructor : currentType.getConstructors()) {
- if (Arrays.equals(argumentsType,
- constructor.getParameterTypes())) {
- return (Constructor<E>) constructor;
- }
- }
- currentType = currentType.getSuperclass();
- }
- return null;
- }
- }
Guiceでは、Spring3 + MyBatis3で使用したapplicationContext.xml、sqlMapConfig.xml、database.propertiesが不要になります。
かわりに、Guiceの場合はソースコードに設定を記述します。
以下が実行するテストコードです。beforeメソッド内の処理が設定になります。
- UserServiceTestGuice.java
-
- package com.brightgenerous.sample.application.service;
- import java.lang.reflect.Method;
- import java.util.Properties;
- import java.util.Set;
- import com.brightgenerous.extension.ibatis.TransactionalMethodInterceptor;
- import com.brightgenerous.sample.application.bean.User;
- import com.brightgenerous.sample.application.dao.UserDao;
- import org.aopalliance.intercept.MethodInterceptor;
- import org.apache.ibatis.io.ResolverUtil;
- import org.apache.ibatis.session.AutoMappingBehavior;
- import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
- import org.junit.Before;
- import org.junit.Test;
- import org.mybatis.guice.MyBatisModule;
- import org.mybatis.guice.datasource.builtin.PooledDataSourceProvider;
- import org.mybatis.guice.transactional.Transactional;
- import com.google.inject.AbstractModule;
- import com.google.inject.Guice;
- import com.google.inject.Injector;
- import com.google.inject.Scopes;
- import com.google.inject.matcher.AbstractMatcher;
- import com.google.inject.name.Names;
- public class UserServiceTestGuice {
- private Injector injector;
- @Before
- public void before() {
- injector = Guice.createInjector(new MyBatisModule() {
- @Override
- protected void initialize() {
- MethodInterceptor updateInterceptor = new TransactionalMethodInterceptor();
- binder().requestInjection(updateInterceptor);
- binder().bindInterceptor(new AbstractMatcher<Class<?>>() {
- @Override
- public boolean matches(Class<?> x_arg0) {
- return x_arg0.getName().matches("^.*Service$");
- }
- }, new AbstractMatcher<Method>() {
- @Override
- public boolean matches(Method x_arg0) {
- return (x_arg0.getAnnotation(Transactional.class) == null)
- && x_arg0.getName().matches(
- "^(regist|update|delete).*$");
- }
- }, updateInterceptor);
- MethodInterceptor readInterceptor = new TransactionalMethodInterceptor(
- true);
- binder().requestInjection(readInterceptor);
- binder().bindInterceptor(new AbstractMatcher<Class<?>>() {
- @Override
- public boolean matches(Class<?> x_arg0) {
- return x_arg0.getName().matches("^.*Service$");
- }
- }, new AbstractMatcher<Method>() {
- @Override
- public boolean matches(Method x_arg0) {
- return (x_arg0.getAnnotation(Transactional.class) == null)
- && !x_arg0.getName().matches(
- "^(regist|update|delete).*$");
- }
- }, readInterceptor);
- environmentId("test");
- bindDataSourceProviderType(PooledDataSourceProvider.class);
- bindTransactionFactoryType(JdbcTransactionFactory.class);
- Properties properties = new Properties();
- properties.setProperty("JDBC.url",
- "jdbc:postgresql://127.0.0.1:5432/scheme");
- properties.setProperty("JDBC.driver", "org.postgresql.Driver");
- properties.setProperty("JDBC.username", "username");
- properties.setProperty("JDBC.password", "password");
- properties.setProperty("JDBC.autoCommit", "false");
- Names.bindProperties(binder(), properties);
- useCacheEnabled(true);
- lazyLoadingEnabled(true);
- aggressiveLazyLoading(true);
- autoMappingBehavior(AutoMappingBehavior.PARTIAL);
- useColumnLabel(true);
- useGeneratedKeys(false);
- failFast(true);
- multipleResultSetsEnabled(true);
- addSimpleAliases("com.brightgenerous.sample.application.bean");
- addMapperClasses(new ResolverUtil<Object>().find(
- new ResolverUtil.Test() {
- @Override
- public boolean matches(Class<?> x_arg0) {
- return x_arg0.getName().matches("^.*Dao$");
- }
- }, "com.brightgenerous.sample.application.dao")
- .getClasses());
- }
- },
- new AbstractModule() {
- @Override
- protected void configure() {
- Set<Class<? extends Object>> services = new ResolverUtil<Object>()
- .find(new ResolverUtil.Test() {
- @Override
- public boolean matches(Class<?> x_arg0) {
- return x_arg0.getName().matches("^.*Service$");
- }
- }, "com.brightgenerous.sample.application.service")
- .getClasses();
- for (Class<? extends Object> service : services) {
- bind(service).in(Scopes.SINGLETON);
- }
- }
- });
- }
- @Test
- public void test01() {
- UserService service = injector.getInstance(UserService.class);
- User user = new User();
- user.setId(Integer.valueOf(1));
- user = service.get(user);
- System.out.println(user.getName());
- user.setName(user.getName() + "-");
- service.update(user);
- System.out.println(service.get(user).getName());
- System.out.println(service.equals(injector
- .getInstance(UserService.class)));
- System.out.println(injector.getInstance(UserDao.class).equals(
- injector.getInstance(UserDao.class)));
- }
- }
使ってみると、Guiceもかなり便利でした。
今回は、あえてSpringの場合と同じ設定にするためにトランザクションインターセプターを自作しましたが、
標準のトランザクションインターセプターとTransactionalアノテーションを使う方法が一般的だと思います。
設定をソースコードに記述するフレームワークとして、Wicketと相性が良さそうです。