Skip to content

Instantly share code, notes, and snippets.

@alexey-su
Created December 8, 2014 09:46
Show Gist options
  • Save alexey-su/567495a9d29b0d175b7c to your computer and use it in GitHub Desktop.
Save alexey-su/567495a9d29b0d175b7c 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 620c9f9..659b8de 100644
--- a/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
+++ b/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
@@ -15,21 +15,23 @@
*/
package org.mybatis.guice;
-import com.google.inject.AbstractModule;
-import com.google.inject.Binder;
-import com.google.inject.Scopes;
+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 static com.google.inject.name.Names.named;
+import static com.google.inject.util.Providers.guicify;
+
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionManager;
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 static com.google.inject.matcher.Matchers.annotatedWith;
-import static com.google.inject.matcher.Matchers.any;
-import static com.google.inject.matcher.Matchers.not;
-import static com.google.inject.name.Names.named;
-import static com.google.inject.util.Providers.guicify;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.Scopes;
/**
*
@@ -54,11 +56,14 @@ abstract class AbstractMyBatisModule extends AbstractModule {
// transactional interceptor
TransactionalMethodInterceptor interceptor = new TransactionalMethodInterceptor();
requestInjection(interceptor);
- bindInterceptor(any(), annotatedWith(Transactional.class), interceptor);
+ TxTransactionalMethodInterceptor interceptorTx = new TxTransactionalMethodInterceptor();
+ requestInjection(interceptorTx);
+
+ 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)), interceptor);
+ bindInterceptor(annotatedWith(Transactional.class), not(annotatedWith(Transactional.class)), interceptorTx, interceptor);
internalConfigure();
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..89fa46c 100644
--- a/src/main/java/org/mybatis/guice/transactional/Transactional.java
+++ b/src/main/java/org/mybatis/guice/transactional/Transactional.java
@@ -15,14 +15,14 @@
*/
package org.mybatis.guice.transactional;
-import org.apache.ibatis.session.ExecutorType;
-import org.apache.ibatis.session.TransactionIsolationLevel;
-
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.TransactionIsolationLevel;
+
/**
* Any method marked with this annotation will be considered for
* transactionality.
@@ -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..844edb0
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java
@@ -0,0 +1,147 @@
+/*
+ * 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;
+
+import com.google.inject.Binding;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+
+/**
+ * Method interceptor for {@link Transactional} annotation.
+ *
+ */
+public class TxTransactionalMethodInterceptor implements MethodInterceptor {
+ /**
+ * This class logger.
+ */
+ private final Log log = LogFactory.getLog(getClass());
+
+ @Inject private Injector injector;
+
+ 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;
+ TransactionManager man = null;
+ TransactionAttribute attribute = null;
+
+ Binding<TransactionManager> binding = injector.getExistingBinding(Key.get(TransactionManager.class));
+ if(binding != null) {
+ man = binding.getProvider().get();
+ }
+ if(man != 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; // TransactionAttribute.SUPPORTS; // FIXME add implementation
+ else if(TxType.NEVER.equals(txType))
+ attribute = TransactionAttribute.NEVER;
+
+ if(attribute == null)
+ man = null;
+ }
+ if(man == null) {
+ if(log.isDebugEnabled()) {
+ log.debug(format("%s - skip Tx Transaction %s",
+ debugPrefix,
+ attribute.name()));
+ }
+
+ // 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(man);
+
+ 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()));
+ }
+ man.setRollbackOnly();
+ throw t;
+ } finally {
+ if(log.isDebugEnabled()) {
+ log.debug(format("%s - Tx Transaction %s (CompletionAllowed %s) finish",
+ debugPrefix,
+ attribute.name(),
+ tranToken.isCompletionAllowed()));
+ }
+ attribute.finish(man, tranToken);
+ }
+ }
+ return object;
+ }
+
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment