Skip to content

Instantly share code, notes, and snippets.

@alexey-su
Created January 30, 2015 19:21
Show Gist options
  • Save alexey-su/d1b05460c7fc59aab60e to your computer and use it in GitHub Desktop.
Save alexey-su/d1b05460c7fc59aab60e to your computer and use it in GitHub Desktop.
diff --git a/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java b/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
index 6b9c745..fd71db7 100644
--- a/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
+++ b/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
@@ -25,6 +25,7 @@ import org.mybatis.guice.mappers.MapperProvider;
import org.mybatis.guice.session.SqlSessionManagerProvider;
import org.mybatis.guice.transactional.Transactional;
import org.mybatis.guice.transactional.TransactionalMethodInterceptor;
+import org.mybatis.guice.transactional.TxTransactionalMethodInterceptor;
import java.lang.reflect.Method;
@@ -60,14 +61,7 @@ abstract class AbstractMyBatisModule extends AbstractModule {
bind(SqlSessionManager.class).toProvider(SqlSessionManagerProvider.class).in(Scopes.SINGLETON);
bind(SqlSession.class).to(SqlSessionManager.class).in(Scopes.SINGLETON);
- // transactional interceptor
- TransactionalMethodInterceptor interceptor = new TransactionalMethodInterceptor();
- requestInjection(interceptor);
- bindInterceptor(any(), not(DECLARED_BY_OBJECT).and(annotatedWith(Transactional.class)), interceptor);
- // Intercept classes annotated with Transactional, but avoid "double"
- // interception when a mathod is also annotated inside an annotated
- // class.
- bindInterceptor(annotatedWith(Transactional.class), not(DECLARED_BY_OBJECT).and(not(annotatedWith(Transactional.class))), interceptor);
+ bindTransactionInterceptors();
internalConfigure();
@@ -79,6 +73,21 @@ abstract class AbstractMyBatisModule extends AbstractModule {
driverClassLoader = getDefaultClassLoader();
}
}
+
+ /**
+ * bind transactional interceptors
+ */
+ protected void bindTransactionInterceptors() {
+ // transactional interceptor
+ TransactionalMethodInterceptor interceptor = new TransactionalMethodInterceptor();
+ requestInjection(interceptor);
+
+ bindInterceptor(any(), annotatedWith(Transactional.class), interceptor);
+ // Intercept classes annotated with Transactional, but avoid "double"
+ // interception when a mathod is also annotated inside an annotated
+ // class.
+ bindInterceptor(annotatedWith(Transactional.class), not(annotatedWith(Transactional.class)), interceptor);
+ }
/**
*
diff --git a/src/main/java/org/mybatis/guice/MyBatisJtaModule.java b/src/main/java/org/mybatis/guice/MyBatisJtaModule.java
new file mode 100644
index 0000000..45d7574
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/MyBatisJtaModule.java
@@ -0,0 +1,80 @@
+package org.mybatis.guice;
+
+import static com.google.inject.matcher.Matchers.annotatedWith;
+import static com.google.inject.matcher.Matchers.any;
+import static com.google.inject.matcher.Matchers.not;
+
+import javax.inject.Provider;
+import javax.transaction.TransactionManager;
+
+import org.apache.ibatis.transaction.TransactionFactory;
+import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
+import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
+import org.mybatis.guice.transactional.Transactional;
+import org.mybatis.guice.transactional.TransactionalMethodInterceptor;
+import org.mybatis.guice.transactional.TxTransactionalMethodInterceptor;
+
+public abstract class MyBatisJtaModule extends MyBatisModule {
+
+ private TransactionManager transactionManager;
+
+
+ public MyBatisJtaModule() {
+ }
+
+ public MyBatisJtaModule(TransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+
+ @Override
+ protected void bindTransactionInterceptors() {
+ TransactionManager manager = getTransactionManager();
+
+ if(manager == null) {
+ super.bindTransactionInterceptors();
+ } else {
+ // mybatis transactional interceptor
+ TransactionalMethodInterceptor interceptor = new TransactionalMethodInterceptor();
+ requestInjection(interceptor);
+
+ // jta transactional interceptor
+ TxTransactionalMethodInterceptor interceptorTx = new TxTransactionalMethodInterceptor();
+ requestInjection(interceptorTx);
+
+ bind(TransactionManager.class).toInstance(manager);
+ bindInterceptor(any(), annotatedWith(Transactional.class), interceptorTx, interceptor);
+ // Intercept classes annotated with Transactional, but avoid "double"
+ // interception when a mathod is also annotated inside an annotated
+ // class.
+ bindInterceptor(annotatedWith(Transactional.class), not(annotatedWith(Transactional.class)), interceptorTx, interceptor);
+ }
+ }
+
+ protected TransactionManager getTransactionManager() {
+ return transactionManager;
+ }
+
+ protected void setTransactionManager(TransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+
+ protected void bindDefaultTransactionProvider() {
+ Class<? extends TransactionFactory> factoryType = getTransactionManager() == null ?
+ JdbcTransactionFactory.class : ManagedTransactionFactory.class;
+
+ bindTransactionFactoryType(factoryType);
+ }
+
+ protected static class ProviderImpl<T> implements Provider<T> {
+ private T wrapper;
+
+ public ProviderImpl(T wrapper) {
+ this.wrapper = wrapper;
+ }
+
+ public T get() {
+ return wrapper;
+ }
+
+ }
+}
diff --git a/src/main/java/org/mybatis/guice/transactional/TransactionAttribute.java b/src/main/java/org/mybatis/guice/transactional/TransactionAttribute.java
new file mode 100644
index 0000000..07b4cc7
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/transactional/TransactionAttribute.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.mybatis.guice.transactional;
+
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+
+public enum TransactionAttribute {
+ MANDATORY
+ {
+ @Override
+ public TransactionToken begin(TransactionManager man) throws SystemException
+ {
+ if (man.getStatus() == Status.STATUS_NO_TRANSACTION) {
+ throw new IllegalStateException(
+ "A call is being made on a method that mandates a transaction but there is no current transaction.");
+ }
+ return new TransactionToken(man.getTransaction(), null, MANDATORY);
+ }
+ },
+ NEVER
+ {
+ @Override
+ public TransactionToken begin(TransactionManager man) throws SystemException
+ {
+ if (man.getStatus() == Status.STATUS_ACTIVE) {
+ throw new IllegalStateException(
+ "A call is being made on a method that forbids a transaction but there is a current transaction.");
+ }
+ return new TransactionToken(null, null, NEVER);
+ }
+ },
+ NOTSUPPORTED {
+ @Override
+ public TransactionToken begin(TransactionManager man) throws SystemException {
+ if (man.getStatus() == Status.STATUS_ACTIVE) {
+ return new TransactionToken(null, man.suspend(), this);
+ }
+ return new TransactionToken(null, null, NOTSUPPORTED);
+ }
+
+ @Override
+ public void finish(TransactionManager man, TransactionToken tranToken) throws SystemException,
+ InvalidTransactionException, IllegalStateException {
+ Transaction tran = tranToken.getSuspendedTransaction();
+ if (tran != null) {
+ man.resume(tran);
+ }
+ }
+ },
+ REQUIRED {
+ @Override
+ public TransactionToken begin(TransactionManager man) throws SystemException, NotSupportedException {
+ if (man.getStatus() == Status.STATUS_NO_TRANSACTION) {
+ man.begin();
+ return new TransactionToken(man.getTransaction(), null, REQUIRED, true);
+ }
+ return new TransactionToken(man.getTransaction(), null, REQUIRED);
+ }
+
+ @Override
+ public void finish(TransactionManager man, TransactionToken tranToken) throws SystemException,
+ InvalidTransactionException, IllegalStateException, SecurityException, RollbackException,
+ HeuristicMixedException, HeuristicRollbackException {
+
+ if (tranToken.isCompletionAllowed()) {
+ if (man.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
+ man.rollback();
+ } else {
+ man.commit();
+ }
+ }
+ }
+ },
+ REQUIRESNEW
+ {
+ @Override
+ public TransactionToken begin(TransactionManager man) throws SystemException, NotSupportedException,
+ InvalidTransactionException, IllegalStateException
+ {
+ TransactionToken tranToken;
+ if (man.getStatus() == Status.STATUS_ACTIVE) {
+ tranToken = new TransactionToken(null, man.suspend(), REQUIRESNEW);
+ } else {
+ tranToken = new TransactionToken(null, null, REQUIRESNEW);
+ }
+
+ try {
+ man.begin();
+ } catch (SystemException e) {
+ man.resume(tranToken.getSuspendedTransaction());
+ throw e;
+ } catch (NotSupportedException e) {
+ man.resume(tranToken.getSuspendedTransaction());
+ throw e;
+ }
+
+ tranToken.setActiveTransaction(man.getTransaction());
+ tranToken.setCompletionAllowed(true);
+
+ return tranToken;
+ }
+
+ @Override
+ public void finish(TransactionManager man, TransactionToken tranToken) throws SystemException,
+ InvalidTransactionException, IllegalStateException, SecurityException, RollbackException,
+ HeuristicMixedException, HeuristicRollbackException
+ {
+ if (tranToken.isCompletionAllowed()) {
+ if (man.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
+ man.rollback();
+ } else {
+ man.commit();
+ }
+ }
+
+ Transaction tran = tranToken.getSuspendedTransaction();
+ if (tran != null) {
+ man.resume(tran);
+ }
+ }
+ },
+ SUPPORTS {
+ @Override
+ public TransactionToken begin(TransactionManager man) throws SystemException, NotSupportedException,
+ InvalidTransactionException, IllegalStateException
+ {
+ if (man.getStatus() == Status.STATUS_ACTIVE) {
+ return new TransactionToken(man.getTransaction(), null, SUPPORTS);
+ }
+
+ return new TransactionToken(null, null, SUPPORTS);
+ }
+ };
+
+ public static TransactionAttribute fromValue(String value) {
+ return valueOf(value.toUpperCase());
+ }
+
+ public TransactionToken begin(TransactionManager man) throws SystemException, NotSupportedException,
+ InvalidTransactionException, IllegalStateException {
+
+ return null;
+ }
+
+ public void finish(TransactionManager man, TransactionToken tranToken) throws SystemException,
+ InvalidTransactionException, IllegalStateException, SecurityException, RollbackException,
+ HeuristicMixedException, HeuristicRollbackException {
+
+ }
+}
diff --git a/src/main/java/org/mybatis/guice/transactional/TransactionToken.java b/src/main/java/org/mybatis/guice/transactional/TransactionToken.java
new file mode 100644
index 0000000..8b1f364
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/transactional/TransactionToken.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.mybatis.guice.transactional;
+
+import javax.transaction.Transaction;
+
+public class TransactionToken {
+ private Transaction activeTransaction;
+ private Transaction suspendedTransaction;
+ private TransactionAttribute transactionAttribute;
+ private boolean isCompletionAllowed;
+
+ public TransactionToken(Transaction activeTransaction, Transaction suspendedTransaction,
+ TransactionAttribute transactionAttribute) {
+ this(activeTransaction, suspendedTransaction, transactionAttribute, false);
+ }
+
+ TransactionToken(Transaction activeTransaction, Transaction suspendedTransaction,
+ TransactionAttribute transactionAttribute, boolean isCompletionAllowed) {
+ this.activeTransaction = activeTransaction;
+ this.suspendedTransaction = suspendedTransaction;
+ this.transactionAttribute = transactionAttribute;
+ this.isCompletionAllowed = isCompletionAllowed;
+ }
+
+ public Transaction getActiveTransaction() {
+ return activeTransaction;
+ }
+
+ public void setActiveTransaction(Transaction activeTransaction) {
+ this.activeTransaction = activeTransaction;
+ }
+
+ public Transaction getSuspendedTransaction() {
+ return suspendedTransaction;
+ }
+
+ public void setSuspendedTransaction(Transaction suspendedTransaction) {
+ this.suspendedTransaction = suspendedTransaction;
+ }
+
+ public TransactionAttribute getTransactionAttribute() {
+ return transactionAttribute;
+ }
+
+ public void setTransactionStrategy(TransactionAttribute transactionAttribute) {
+ this.transactionAttribute = transactionAttribute;
+ }
+
+ public boolean isCompletionAllowed() {
+ return isCompletionAllowed;
+ }
+
+ public void setCompletionAllowed(boolean isCompletionAllowed) {
+ this.isCompletionAllowed = isCompletionAllowed;
+ }
+}
diff --git a/src/main/java/org/mybatis/guice/transactional/Transactional.java b/src/main/java/org/mybatis/guice/transactional/Transactional.java
index 970f768..87883a5 100644
--- a/src/main/java/org/mybatis/guice/transactional/Transactional.java
+++ b/src/main/java/org/mybatis/guice/transactional/Transactional.java
@@ -101,4 +101,103 @@ public @interface Transactional {
*/
boolean rollbackOnly() default false;
+ //
+ // from javax.transaction.Transactional
+ //
+
+ /**
+ * The TxType element of the Transactional annotation indicates whether a bean method
+ * is to be executed within a transaction context.
+ */
+ TxType value() default TxType.REQUIRED;
+
+ /**
+ * The TxType element of the annotation indicates whether a bean method is to be
+ * executed within a transaction context where the values provide the following
+ * corresponding behavior.
+ */
+ public enum TxType {
+ /**
+ * <p>If called outside a transaction context, the interceptor must begin a new
+ * JTA transaction, the managed bean method execution must then continue
+ * inside this transaction context, and the transaction must be completed by
+ * the interceptor.</p>
+ * <p>If called inside a transaction context, the managed bean
+ * method execution must then continue inside this transaction context.</p>
+ */
+ REQUIRED,
+
+ /**
+ * <p>If called outside a transaction context, the interceptor must begin a new
+ * JTA transaction, the managed bean method execution must then continue
+ * inside this transaction context, and the transaction must be completed by
+ * the interceptor.</p>
+ * <p>If called inside a transaction context, the current transaction context must
+ * be suspended, a new JTA transaction will begin, the managed bean method
+ * execution must then continue inside this transaction context, the transaction
+ * must be completed, and the previously suspended transaction must be resumed.</p>
+ */
+ REQUIRES_NEW,
+
+ /**
+ * <p>If called outside a transaction context, a TransactionalException with a
+ * nested TransactionRequiredException must be thrown.</p>
+ * <p>If called inside a transaction context, managed bean method execution will
+ * then continue under that context.</p>
+ */
+ MANDATORY,
+
+ /**
+ * <p>If called outside a transaction context, managed bean method execution
+ * must then continue outside a transaction context.</p>
+ * <p>If called inside a transaction context, the managed bean method execution
+ * must then continue inside this transaction context.</p>
+ */
+ SUPPORTS,
+
+ /**
+ * <p>If called outside a transaction context, managed bean method execution
+ * must then continue outside a transaction context.</p>
+ * <p>If called inside a transaction context, the current transaction context must
+ * be suspended, the managed bean method execution must then continue
+ * outside a transaction context, and the previously suspended transaction
+ * must be resumed by the interceptor that suspended it after the method
+ * execution has completed.</p>
+ */
+ NOT_SUPPORTED,
+
+ /**
+ * <p>If called outside a transaction context, managed bean method execution
+ * must then continue outside a transaction context.</p>
+ * <p>If called inside a transaction context, a TransactionalException with
+ * a nested InvalidTransactionException must be thrown.</p>
+ */
+ NEVER
+ }
+
+ /**
+ * The rollbackOn element can be set to indicate exceptions that must cause
+ * the interceptor to mark the transaction for rollback. Conversely, the dontRollbackOn
+ * element can be set to indicate exceptions that must not cause the interceptor to mark
+ * the transaction for rollback. When a class is specified for either of these elements,
+ * the designated behavior applies to subclasses of that class as well. If both elements
+ * are specified, dontRollbackOn takes precedence.
+ * @return Class[] of Exceptions
+ */
+ // @Nonbinding
+ // public Class[] rollbackOn() default {};
+
+ /**
+ * The dontRollbackOn element can be set to indicate exceptions that must not cause
+ * the interceptor to mark the transaction for rollback. Conversely, the rollbackOn element
+ * can be set to indicate exceptions that must cause the interceptor to mark the transaction
+ * for rollback. When a class is specified for either of these elements,
+ * the designated behavior applies to subclasses of that class as well. If both elements
+ * are specified, dontRollbackOn takes precedence.
+ * @return Class[] of Exceptions
+ */
+ // @Nonbinding
+ // public Class[] dontRollbackOn() default {};
+
+
}
diff --git a/src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java b/src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java
new file mode 100644
index 0000000..fdc991c
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2010-2014 The MyBatis Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mybatis.guice.transactional;
+
+import static java.lang.String.format;
+
+import java.lang.reflect.Method;
+
+import javax.inject.Inject;
+import javax.transaction.TransactionManager;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+import org.mybatis.guice.transactional.Transactional.TxType;
+
+/**
+ * Method interceptor for {@link Transactional} annotation.
+ *
+ */
+public class TxTransactionalMethodInterceptor implements MethodInterceptor {
+ /**
+ * This class logger.
+ */
+ private final Log log = LogFactory.getLog(getClass());
+
+ @Inject private TransactionManager manager;
+
+ public TxTransactionalMethodInterceptor() {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object invoke(MethodInvocation invocation) throws Throwable {
+ Method interceptedMethod = invocation.getMethod();
+ Transactional transactional = interceptedMethod.getAnnotation(Transactional.class);
+
+ // The annotation may be present at the class level instead
+ if (transactional == null) {
+ transactional = interceptedMethod.getDeclaringClass().getAnnotation(Transactional.class);
+ }
+
+ String debugPrefix = null;
+ if (this.log.isDebugEnabled()) {
+ debugPrefix = String.format("[Intercepted method: %s]", interceptedMethod.toGenericString());
+ }
+
+ Object object = null;
+ TransactionAttribute attribute = null;
+
+ if(manager != null) {
+ TxType txType = transactional.value();
+ if(TxType.REQUIRED.equals(txType))
+ attribute = TransactionAttribute.REQUIRED;
+ else if(TxType.REQUIRES_NEW.equals(txType))
+ attribute = TransactionAttribute.REQUIRESNEW;
+ else if(TxType.MANDATORY.equals(txType))
+ attribute = TransactionAttribute.MANDATORY;
+ else if(TxType.SUPPORTS.equals(txType))
+ attribute = TransactionAttribute.SUPPORTS;
+ else if(TxType.NOT_SUPPORTED.equals(txType))
+ attribute = null; // FIXME add implementation
+ else if(TxType.NEVER.equals(txType))
+ attribute = TransactionAttribute.NEVER;
+ }
+
+ if(attribute == null) {
+ if(log.isDebugEnabled()) {
+ log.debug(format("%s - skip Tx Transaction", debugPrefix));
+ }
+
+ // without Tx
+ try {
+ object = invocation.proceed();
+ } catch (Throwable t) {
+ throw t;
+ }
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(format("%s - Tx Transaction %s begin",
+ debugPrefix,
+ attribute.name()));
+ }
+
+ // with Tx
+ TransactionToken tranToken = attribute.begin(manager);
+
+ try {
+ if(log.isDebugEnabled()) {
+ log.debug(format("%s - Tx Transaction %s (CompletionAllowed %s) call method",
+ debugPrefix,
+ attribute.name(),
+ tranToken.isCompletionAllowed()));
+ }
+ object = invocation.proceed();
+
+ } catch (Throwable t) {
+ if(log.isDebugEnabled()) {
+ log.debug(format("%s - Tx Transaction %s (CompletionAllowed %s) rolling back",
+ debugPrefix,
+ attribute.name(),
+ tranToken.isCompletionAllowed()));
+ }
+ manager.setRollbackOnly();
+ throw t;
+ } finally {
+ if(log.isDebugEnabled()) {
+ log.debug(format("%s - Tx Transaction %s (CompletionAllowed %s) finish",
+ debugPrefix,
+ attribute.name(),
+ tranToken.isCompletionAllowed()));
+ }
+ attribute.finish(manager, tranToken);
+ }
+ }
+ return object;
+ }
+
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment