Skip to content

Instantly share code, notes, and snippets.

@christianpoitras
Last active August 29, 2015 14:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save christianpoitras/34b38a0328b9a6114d4d to your computer and use it in GitHub Desktop.
Save christianpoitras/34b38a0328b9a6114d4d to your computer and use it in GitHub Desktop.
From abbdb24237d8e6cd1a76f8d729f4c3ab4be04dfe Mon Sep 17 00:00:00 2001
From: Aleksey Sushko <alexey.l.sushko@gmail.com>
Date: Mon, 9 Mar 2015 22:05:49 +0300
Subject: [PATCH 1/6] Add Tx Transaction interceptor #29
---
pom.xml | 47 ++-
.../org/mybatis/guice/AbstractMyBatisModule.java | 23 +-
.../java/org/mybatis/guice/MyBatisJtaModule.java | 86 +++++
.../guice/transactional/TransactionAttribute.java | 171 ++++++++++
.../guice/transactional/TransactionToken.java | 70 ++++
.../mybatis/guice/transactional/Transactional.java | 99 ++++++
.../TxTransactionalMethodInterceptor.java | 144 +++++++++
.../guice/transactional/XASqlSessionManager.java | 355 +++++++++++++++++++++
src/test/java/org/mybatis/guice/jta/BaseDB.java | 124 +++++++
.../mybatis/guice/jta/JtaLocalRollbackTest.java | 142 +++++++++
.../java/org/mybatis/guice/jta/JtaLocalTest.java | 329 +++++++++++++++++++
src/test/java/org/mybatis/guice/jta/JtaMapper.java | 34 ++
.../java/org/mybatis/guice/jta/JtaProcess.java | 268 ++++++++++++++++
.../mybatis/guice/jta/JtaRollbackException.java | 8 +
.../java/org/mybatis/guice/jta/JtaService.java | 11 +
.../org/mybatis/guice/jta/JtaService1Impl.java | 36 +++
.../org/mybatis/guice/jta/JtaService2Impl.java | 36 +++
.../org/mybatis/guice/jta/JtaXaRollbackTest.java | 142 +++++++++
src/test/java/org/mybatis/guice/jta/JtaXaTest.java | 328 +++++++++++++++++++
src/test/java/org/mybatis/guice/jta/TableRow.java | 37 +++
.../resources/org/mybatis/guice/jta/setupdb.sql | 30 ++
.../resources/org/mybatis/guice/jta/shutdowndb.sql | 21 ++
22 files changed, 2532 insertions(+), 9 deletions(-)
create mode 100644 src/main/java/org/mybatis/guice/MyBatisJtaModule.java
create mode 100644 src/main/java/org/mybatis/guice/transactional/TransactionAttribute.java
create mode 100644 src/main/java/org/mybatis/guice/transactional/TransactionToken.java
create mode 100644 src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java
create mode 100644 src/main/java/org/mybatis/guice/transactional/XASqlSessionManager.java
create mode 100644 src/test/java/org/mybatis/guice/jta/BaseDB.java
create mode 100644 src/test/java/org/mybatis/guice/jta/JtaLocalRollbackTest.java
create mode 100644 src/test/java/org/mybatis/guice/jta/JtaLocalTest.java
create mode 100644 src/test/java/org/mybatis/guice/jta/JtaMapper.java
create mode 100644 src/test/java/org/mybatis/guice/jta/JtaProcess.java
create mode 100644 src/test/java/org/mybatis/guice/jta/JtaRollbackException.java
create mode 100644 src/test/java/org/mybatis/guice/jta/JtaService.java
create mode 100644 src/test/java/org/mybatis/guice/jta/JtaService1Impl.java
create mode 100644 src/test/java/org/mybatis/guice/jta/JtaService2Impl.java
create mode 100644 src/test/java/org/mybatis/guice/jta/JtaXaRollbackTest.java
create mode 100644 src/test/java/org/mybatis/guice/jta/JtaXaTest.java
create mode 100644 src/test/java/org/mybatis/guice/jta/TableRow.java
create mode 100644 src/test/resources/org/mybatis/guice/jta/setupdb.sql
create mode 100644 src/test/resources/org/mybatis/guice/jta/shutdowndb.sql
diff --git a/pom.xml b/pom.xml
index 019094e..86ca24f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,7 +85,7 @@
<findbugs.onlyAnalyze>org.mybatis.guice.*</findbugs.onlyAnalyze>
<gcu.product>Guice</gcu.product>
<guice.version>3.0</guice.version>
- <osgi.import>com.jolbox.bonecp.*;resolution:=optional,com.mchange.v2.c3p0.*;resolution:=optional,org.apache.commons.dbcp.*;resolution:=optional,*</osgi.import>
+ <osgi.import>com.jolbox.bonecp.*;resolution:=optional,com.mchange.v2.c3p0.*;resolution:=optional,org.apache.commons.dbcp.*;resolution:=optional,*,javax.transaction.*;resolution:=optional</osgi.import>
</properties>
<dependencies>
@@ -169,6 +169,51 @@
<version>1.7.10</version>
<scope>test</scope>
</dependency>
+
+ <dependency>
+ <groupId>org.apache.openejb</groupId>
+ <artifactId>javaee-api</artifactId>
+ <version>6.0-5</version>
+ <optional>true</optional>
+ </dependency>
+
+ <!-- JTA test -->
+ <dependency>
+ <groupId>org.apache.aries.transaction</groupId>
+ <artifactId>org.apache.aries.transaction.manager</artifactId>
+ <version>1.1.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.tranql</groupId>
+ <artifactId>tranql-connector</artifactId>
+ <version>1.8</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.components</groupId>
+ <artifactId>geronimo-connector</artifactId>
+ <version>3.1.1</version>
+ <!-- <scope>test</scope> -->
+ </dependency>
+ <dependency>
+ <groupId>org.apache.aries.transaction</groupId>
+ <artifactId>org.apache.aries.transaction.jdbc</artifactId>
+ <version>2.1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.aries.transaction</groupId>
+ <artifactId>org.apache.aries.transaction.blueprint</artifactId>
+ <version>1.0.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derby</artifactId>
+ <version>10.11.1.1</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java b/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
index e9afc7c..f59f867 100644
--- a/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
+++ b/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
@@ -60,14 +60,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 +72,20 @@ abstract class AbstractMyBatisModule extends AbstractModule {
driverClassLoader = getDefaultClassLoader();
}
}
+
+ /**
+ * bind transactional interceptors
+ */
+ protected void bindTransactionInterceptors() {
+ // 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);
+ }
/**
*
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..245b051
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/MyBatisJtaModule.java
@@ -0,0 +1,86 @@
+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.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+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 final Log log = LogFactory.getLog(getClass());
+
+ private TransactionManager transactionManager;
+
+ public MyBatisJtaModule() {
+ }
+
+ public MyBatisJtaModule(TransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+
+ @Override
+ protected void bindTransactionInterceptors() {
+ TransactionManager manager = getTransactionManager();
+
+ if (manager == null) {
+ log.debug("bind default transaction interceptors");
+ super.bindTransactionInterceptors();
+ } else {
+ log.debug("bind XA transaction interceptors");
+
+ // 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..2315464
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/transactional/TransactionAttribute.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2010-2015 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 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..ee38d41
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/transactional/TransactionToken.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010-2015 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 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 ce3a14a..5f44a83 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..643135e
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2010-2015 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.apache.ibatis.session.SqlSessionManager;
+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;
+ @Inject private SqlSessionManager sqlSessionManager;
+
+ 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());
+ }
+
+ boolean needsRollback = transactional.rollbackOnly();
+ 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);
+
+ log.debug("enlistResource XASqlSessionManager");
+ XASqlSessionManager xaRes = new XASqlSessionManager(sqlSessionManager);
+ tranToken.getActiveTransaction().enlistResource(xaRes);
+
+ try {
+ if(log.isDebugEnabled()) {
+ log.debug(format("%s - Tx Transaction %s (CompletionAllowed %s) call method",
+ debugPrefix,
+ attribute.name(),
+ tranToken.isCompletionAllowed()));
+ }
+ object = invocation.proceed();
+
+ if(needsRollback)
+ manager.setRollbackOnly();
+
+ } 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;
+ }
+
+}
diff --git a/src/main/java/org/mybatis/guice/transactional/XASqlSessionManager.java b/src/main/java/org/mybatis/guice/transactional/XASqlSessionManager.java
new file mode 100644
index 0000000..13a97fe
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/transactional/XASqlSessionManager.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2010-2015 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 java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.apache.geronimo.transaction.manager.NamedXAResource;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionManager;
+
+public class XASqlSessionManager implements XAResource, NamedXAResource {
+ private static final Log log = LogFactory.getLog(XASqlSessionManager.class);
+
+ private SqlSessionManager sqlSessionManager;
+ private int transactionTimeout;
+ private String id;
+
+ private static HashMap<GlobalKey, GlobalToken> globalTokens = new HashMap<XASqlSessionManager.GlobalKey, XASqlSessionManager.GlobalToken>();
+
+ public XASqlSessionManager(SqlSessionManager sqlSessionManager) {
+ this.sqlSessionManager = sqlSessionManager;
+ id = sqlSessionManager.getConfiguration().getEnvironment().getId();
+ }
+
+ //@Override
+ public String getName() {
+ return id;
+ }
+
+ //@Override
+ public void commit(Xid xid, boolean flag) throws XAException {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " commit flag:" + flag + " " + xid);
+ }
+ parentResume(xid);
+ }
+
+ //@Override
+ public void end(Xid xid, int flag) throws XAException {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " end flag:" + printFlag(flag) + " " + xid);
+ }
+
+ // TODO XAResource.TMSUSPEND ?
+ }
+
+ //@Override
+ public void forget(Xid xid) throws XAException {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " forget " + xid);
+ }
+ }
+
+ //@Override
+ public int getTransactionTimeout() throws XAException {
+ return transactionTimeout;
+ }
+
+ //@Override
+ public boolean isSameRM(XAResource xares) throws XAException {
+ return this == xares;
+ }
+
+ //@Override
+ public int prepare(Xid xid) throws XAException {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " prepare " + xid);
+ }
+ return 0;
+ }
+
+ //@Override
+ public Xid[] recover(int flag) throws XAException {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " recover flag:" + printFlag(flag));
+ }
+
+ // TODO return empty array or null? or current xid?
+ return new Xid[0];
+ }
+
+ //@Override
+ public void rollback(Xid xid) throws XAException {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " rollback " + xid);
+ }
+
+ parentResume(xid);
+ }
+
+ //@Override
+ public boolean setTransactionTimeout(int second) throws XAException {
+ transactionTimeout = second;
+ return true;
+ }
+
+ //@Override
+ public void start(Xid xid, int flag) throws XAException {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " start flag:" + printFlag(flag) + " " + xid);
+ }
+
+ if(flag == XAResource.TMNOFLAGS) {
+ parentSuspend(xid);
+ }
+ // TODO XAResource.TMRESUME ?
+ }
+
+ private String printFlag(int flag) {
+ switch(flag) {
+ case XAResource.TMENDRSCAN: return "TMENDRSCAN";
+ case XAResource.TMFAIL: return "TMFAIL";
+ case XAResource.TMJOIN: return "TMJOIN";
+ case XAResource.TMNOFLAGS: return "TMNOFLAGS";
+ case XAResource.TMONEPHASE: return "TMONEPHASE";
+ case XAResource.TMRESUME: return "TMRESUME";
+ case XAResource.TMSTARTRSCAN: return "TMSTARTRSCAN";
+ case XAResource.TMSUCCESS: return "TMSUCCESS";
+ case XAResource.TMSUSPEND: return "TMSUSPEND";
+ default: return "" + flag;
+ }
+ }
+
+ private void parentSuspend(Xid xid) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " suspend parent session " + xid);
+ }
+
+ byte[] trId = xid.getGlobalTransactionId();
+ GlobalKey key = new GlobalKey(trId);
+ GlobalToken globalToken = globalTokens.get(key);
+
+ if(globalToken == null) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " add GlobalToken " + key);
+ }
+
+ globalTokens.put(key, globalToken = new GlobalToken());
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " present GlobalToken " + key);
+ }
+ }
+ globalToken.parentSuspend(id, sqlSessionManager);
+ }
+
+ private void parentResume(Xid xid) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " resume parent session " + xid);
+ }
+
+ byte[] trId = xid.getGlobalTransactionId();
+ GlobalKey key = new GlobalKey(trId);
+ GlobalToken globalToken = globalTokens.get(key);
+
+ if(globalToken != null) {
+ globalToken.parentResume(id, sqlSessionManager);
+
+ if(globalToken.isEmpty()) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " remove GlobalToken " + key);
+ }
+
+ globalTokens.remove(key);
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " not remove GlobalToken " + key);
+ }
+ }
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " not find GlobalToken " + key);
+ }
+ }
+ }
+
+ static class GlobalKey {
+ final byte[] globalId;
+ final int arrayHash;
+
+ public GlobalKey(byte[] globalId) {
+ this.globalId = globalId;
+ this.arrayHash = Arrays.hashCode(globalId);
+ }
+
+ @Override
+ public int hashCode() {
+ return arrayHash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ GlobalKey other = (GlobalKey) obj;
+ if (!Arrays.equals(globalId, other.globalId))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+ s.append("[Xid:globalId=");
+ for (int i = 0; i < globalId.length; i++) {
+ s.append(Integer.toHexString(globalId[i]));
+ }
+ s.append(",length=").append(globalId.length);
+ return s.toString();
+ }
+ }
+
+ static class GlobalToken {
+ private final Log log = LogFactory.getLog(getClass());
+ IdentityHashMap<SqlSessionManager, Token> tokens = new IdentityHashMap<SqlSessionManager, XASqlSessionManager.Token>();
+
+ public GlobalToken() {
+ }
+
+ void parentSuspend(String id, SqlSessionManager sqlSessionManager) {
+ Token token = tokens.get(sqlSessionManager);
+
+ if(token == null) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " add Token " + sqlSessionManager);
+ }
+
+ token = new Token(sqlSessionManager);
+ tokens.put(sqlSessionManager, token);
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " present Token " + sqlSessionManager);
+ }
+ }
+ token.parentSuspend(id);
+ }
+
+ void parentResume(String id, SqlSessionManager sqlSessionManager) {
+ Token token = tokens.get(sqlSessionManager);
+
+ if(token != null) {
+ token.parentResume(id);
+
+ // remove last
+ if(token.isFirst()) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " remove parent session " + sqlSessionManager);
+ }
+
+ tokens.remove(sqlSessionManager);
+ }
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " not find parent session " + sqlSessionManager);
+ }
+ }
+ }
+
+ boolean isEmpty() {
+ return tokens.isEmpty();
+ }
+ }
+
+ static class Token {
+ private final Log log = LogFactory.getLog(getClass());
+ final SqlSessionManager sqlSessionManager;
+ ThreadLocal<SqlSession> localSqlSession;
+ SqlSession suspendedSqlSession;
+ int count;
+
+ @SuppressWarnings("unchecked")
+ public Token(SqlSessionManager sqlSessionManager) {
+ this.sqlSessionManager = sqlSessionManager;
+ this.count = 0;
+ try {
+ Field field = SqlSessionManager.class.getDeclaredField("localSqlSession");
+ field.setAccessible(true);
+ localSqlSession = (ThreadLocal<SqlSession>) field.get(sqlSessionManager);
+ } catch(Exception e) {
+ }
+ }
+
+ boolean isFirst() {
+ return count == 0;
+ }
+
+ void parentSuspend(String id) {
+ if(isFirst()) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " suspend parent session");
+ }
+
+ if(localSqlSession != null) {
+ suspendedSqlSession = localSqlSession.get();
+ localSqlSession.remove();
+ }
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " skip suspend parent session");
+ }
+ }
+ count++;
+ }
+
+ void parentResume(String id) {
+ if(count > 0)
+ count--;
+
+ if(isFirst()) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " resume parent session");
+ }
+
+ if(localSqlSession != null) {
+ if(suspendedSqlSession == null) {
+ localSqlSession.remove();
+ } else {
+ localSqlSession.set(suspendedSqlSession);
+ suspendedSqlSession = null;
+ }
+ }
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " skip resume parent session");
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/java/org/mybatis/guice/jta/BaseDB.java b/src/test/java/org/mybatis/guice/jta/BaseDB.java
new file mode 100644
index 0000000..96c4020
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/BaseDB.java
@@ -0,0 +1,124 @@
+package org.mybatis.guice.jta;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.aries.transaction.AriesTransactionManager;
+import org.apache.aries.transaction.jdbc.RecoverableDataSource;
+import org.apache.derby.jdbc.EmbeddedDataSource;
+import org.apache.derby.jdbc.EmbeddedXADataSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BaseDB {
+ private static final Logger LOGGER = LoggerFactory.getLogger(BaseDB.class);
+
+ public static final String NAME_DB1 = "target/db1";
+ public static final String NAME_DB2 = "target/db2";
+ public static final String URL_DB1 = "jdbc:derby:" + NAME_DB1;
+ public static final String URL_DB2 = "jdbc:derby:" + NAME_DB2;
+ static final String USER = "SA";
+ static final String PASSWORD = "";
+
+ static final String QUERY_CREATE_TABLE = "create table table1 ("
+ + "id integer not null,"
+ + "name varchar(80) not null,"
+ + "constraint pk_table1 primary key (id) )";
+ static final String QUERY_DROP_TABLE = "drop table table1";
+ static final String QUERY_INSERT = "insert into table1 (id, name) values (?,?)";
+ static final String QUERY_SELECT = "select id from table1";
+ static final String QUERY_DELETE = "delete from table1";
+
+
+ public static DataSource createLocalDataSource(String dataSourceName, String dataSourceURL, AriesTransactionManager manager) throws Exception {
+ executeScript(dataSourceURL + ";create=true", QUERY_CREATE_TABLE);
+
+ EmbeddedDataSource localDataSource = new EmbeddedDataSource();
+ localDataSource.setDatabaseName(dataSourceName);
+ localDataSource.setUser(USER);
+ localDataSource.setPassword(PASSWORD);
+
+ RecoverableDataSource recoverableDataSource = new RecoverableDataSource();
+
+ recoverableDataSource.setDataSource(localDataSource);
+ recoverableDataSource.setUsername(USER);
+ recoverableDataSource.setPassword(PASSWORD);
+ recoverableDataSource.setTransactionManager(manager);
+ recoverableDataSource.setTransaction("local");
+ recoverableDataSource.start();
+
+ return recoverableDataSource;
+ }
+
+ public static DataSource createXADataSource(String dataSourceName, String dataSourceURL, AriesTransactionManager manager) throws Exception {
+ String className = "org.apache.derby.jdbc.EmbeddedDriver";
+ Class.forName(className).newInstance();
+
+ executeScript(dataSourceURL + ";create=true", QUERY_CREATE_TABLE);
+
+ EmbeddedXADataSource xaDataSource = new EmbeddedXADataSource();
+ xaDataSource.setDatabaseName(dataSourceName);
+ xaDataSource.setUser(USER);
+ xaDataSource.setPassword(PASSWORD);
+
+ RecoverableDataSource recoverableDataSource = new RecoverableDataSource();
+
+ recoverableDataSource.setDataSource(xaDataSource);
+ recoverableDataSource.setUsername(USER);
+ recoverableDataSource.setPassword(PASSWORD);
+ recoverableDataSource.setTransactionManager(manager);
+ recoverableDataSource.setTransaction("xa");
+ recoverableDataSource.start();
+
+ return recoverableDataSource;
+ }
+
+ public static void dropTable(String dataSourceURL) throws Exception {
+ executeScript(dataSourceURL, QUERY_DROP_TABLE);
+ }
+
+ public static void clearTable(String dataSourceURL) throws Exception {
+ executeScript(dataSourceURL, QUERY_DELETE);
+ }
+
+ public static void insertRow(Connection con, int id, String name) throws Exception {
+ PreparedStatement stmt = con.prepareStatement(QUERY_INSERT);
+ stmt.setInt(1, id);
+ stmt.setString(2, name);
+ stmt.executeUpdate();
+ stmt.close();
+ }
+
+ public static List<Integer> readRows(String dataSourceURL, String dataSourceName) throws Exception {
+ List<Integer> list = new ArrayList<Integer>(2);
+ Connection connection = DriverManager.getConnection(dataSourceURL, USER, PASSWORD);
+ PreparedStatement stmt = connection.prepareStatement(QUERY_SELECT);
+ ResultSet rs = stmt.executeQuery();
+
+ while(rs.next()) {
+ int id = rs.getInt(1);
+ list.add(id);
+ LOGGER.info("read {} from {}", id, dataSourceName);
+ }
+ rs.close();
+ stmt.close();
+ connection.close();
+
+ return list;
+ }
+
+ private static void executeScript(String dataSourceName, String query) throws Exception {
+ Connection connection = DriverManager.getConnection(dataSourceName, USER, PASSWORD);
+ Statement stmt = connection.createStatement();
+ stmt.execute(query);
+ stmt.close();
+ connection.close();
+ }
+}
diff --git a/src/test/java/org/mybatis/guice/jta/JtaLocalRollbackTest.java b/src/test/java/org/mybatis/guice/jta/JtaLocalRollbackTest.java
new file mode 100644
index 0000000..a834648
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/JtaLocalRollbackTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.jta;
+
+import static org.junit.Assert.assertEquals;
+
+import java.sql.Connection;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.aries.transaction.AriesTransactionManager;
+import org.apache.aries.transaction.internal.AriesTransactionManagerImpl;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mybatis.guice.transactional.TransactionAttribute;
+import org.mybatis.guice.transactional.TransactionToken;
+
+/**
+ * Create Requerd transaction. Create internal RequiresNew transaction. Rollback first transaction.
+ * Warning: transaction will roll back. XA error code: 100
+ */
+public class JtaLocalRollbackTest {
+
+ private static DataSource dataSource;
+ private static AriesTransactionManager manager;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
+
+ manager = new AriesTransactionManagerImpl();
+
+ String className = "org.apache.derby.jdbc.EmbeddedDriver";
+ Class.forName(className).newInstance();
+
+ dataSource = BaseDB.createLocalDataSource(BaseDB.NAME_DB1, BaseDB.URL_DB1, manager);
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ BaseDB.dropTable(BaseDB.URL_DB1);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ BaseDB.clearTable(BaseDB.URL_DB1);
+ }
+
+ @Test
+ public void testFirstRollback() throws Exception {
+ TransactionAttribute firstAttribute = TransactionAttribute.REQUIRED;
+ TransactionAttribute secondAttribute = TransactionAttribute.REQUIRESNEW;
+
+ // REQUIRED transaction
+ TransactionToken firstToken = firstAttribute.begin(manager);
+ try {
+ Connection firstCon = dataSource.getConnection();
+ BaseDB.insertRow(firstCon, 1, "name 1");
+ firstCon.close();
+
+ // REQUIRESNEW transaction
+ TransactionToken secondToken = secondAttribute.begin(manager);
+ try {
+ Connection secondCon = dataSource.getConnection();
+ BaseDB.insertRow(secondCon, 2, "name 2");
+ secondCon.close();
+ } finally {
+ secondAttribute.finish(manager, secondToken);
+ }
+
+ // roll back REQUIRED after commit REQUIRESNEW
+ throw new Exception("rollback");
+
+ } catch(Exception e) {
+ manager.setRollbackOnly();
+ } finally {
+ firstAttribute.finish(manager, firstToken);
+ }
+
+ List<Integer> rows = BaseDB.readRows(BaseDB.URL_DB1, BaseDB.NAME_DB1);
+ assertEquals(1, rows.size());
+ assertEquals(2, rows.get(0).intValue());
+ }
+
+ @Test
+ public void testSecondRollback() throws Exception {
+ TransactionAttribute firstAttribute = TransactionAttribute.REQUIRED;
+ TransactionAttribute secondAttribute = TransactionAttribute.REQUIRESNEW;
+
+ // REQUIRED transaction
+ TransactionToken firstToken = firstAttribute.begin(manager);
+ try {
+ Connection firstCon = dataSource.getConnection();
+ BaseDB.insertRow(firstCon, 1, "name 1");
+ firstCon.close();
+
+ // REQUIRESNEW transaction
+ TransactionToken secondToken = secondAttribute.begin(manager);
+ try {
+ Connection secondCon = dataSource.getConnection();
+ BaseDB.insertRow(secondCon, 2, "name 2");
+ secondCon.close();
+
+ // roll back REQUIRESNEW and commit REQUIRED
+ throw new Exception("rollback");
+ } catch(Exception e) {
+ // not throws exception to REQUITED
+ manager.setRollbackOnly();
+ } finally {
+ secondAttribute.finish(manager, secondToken);
+ }
+
+ } catch(Exception e) {
+ manager.setRollbackOnly();
+ } finally {
+ firstAttribute.finish(manager, firstToken);
+ }
+
+ List<Integer> rows = BaseDB.readRows(BaseDB.URL_DB1, BaseDB.NAME_DB1);
+ assertEquals(1, rows.size());
+ assertEquals(1, rows.get(0).intValue());
+ }
+}
diff --git a/src/test/java/org/mybatis/guice/jta/JtaLocalTest.java b/src/test/java/org/mybatis/guice/jta/JtaLocalTest.java
new file mode 100644
index 0000000..db95d8e
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/JtaLocalTest.java
@@ -0,0 +1,329 @@
+package org.mybatis.guice.jta;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.aries.transaction.AriesTransactionManager;
+import org.apache.aries.transaction.internal.AriesTransactionManagerImpl;
+import org.apache.ibatis.logging.LogFactory;
+import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.mybatis.guice.MyBatisJtaModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.PrivateModule;
+
+public class JtaLocalTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(JtaLocalTest.class);
+
+ static AriesTransactionManager manager;
+ static DataSource dataSource1;
+ static DataSource dataSource2;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
+ LogFactory.useSlf4jLogging();
+
+ manager = new AriesTransactionManagerImpl();
+
+ dataSource1 = BaseDB.createLocalDataSource(BaseDB.NAME_DB1, BaseDB.URL_DB1, manager);
+ dataSource2 = BaseDB.createLocalDataSource(BaseDB.NAME_DB2, BaseDB.URL_DB2, manager);
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ BaseDB.dropTable(BaseDB.URL_DB1);
+ BaseDB.dropTable(BaseDB.URL_DB2);
+ }
+
+ @Rule
+ public TestName testName = new TestName();
+ private Injector injector;
+
+ JtaProcess process;
+
+ @Before
+ public void setup() throws Exception {
+ LOGGER.info("********************************************************************************");
+ LOGGER.info("Testing: " + testName.getMethodName() + "(" + getClass().getName() + ")");
+ LOGGER.info("********************************************************************************");
+ LogFactory.useSlf4jLogging();
+
+
+ LOGGER.info("create injector");
+ injector = Guice.createInjector(
+ new PrivateModule() {
+
+ @Override
+ protected void configure() {
+ install(new MyBatisJtaModule(manager) {
+
+ @Override
+ protected void initialize() {
+ environmentId("db1");
+ bindDataSourceProvider(new ProviderImpl<DataSource>(dataSource1));
+ bindDefaultTransactionProvider();
+ bindDatabaseIdProvider(new VendorDatabaseIdProvider());
+
+ addMapperClass(JtaMapper.class);
+
+ bind(JtaService1Impl.class);
+ }
+ });
+
+ expose(JtaService1Impl.class);
+ };
+ },
+ new PrivateModule() {
+
+ @Override
+ protected void configure() {
+ install(new MyBatisJtaModule(manager) {
+
+ @Override
+ protected void initialize() {
+ environmentId("db2");
+ bindDataSourceProvider(new ProviderImpl<DataSource>(dataSource2));
+ bindDefaultTransactionProvider();
+ bindDatabaseIdProvider(new VendorDatabaseIdProvider());
+
+ addMapperClass(JtaMapper.class);
+
+ bind(JtaService2Impl.class);
+ bind(JtaProcess.class);
+ }
+ });
+
+ expose(JtaService2Impl.class);
+ expose(JtaProcess.class);
+ };
+ }
+ );
+
+ injector.injectMembers(this);
+ process = injector.getInstance(JtaProcess.class);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ BaseDB.clearTable(BaseDB.URL_DB1);
+ BaseDB.clearTable(BaseDB.URL_DB2);
+
+ LOGGER.info("********************************************************************************");
+ LOGGER.info("Testing done: " + testName.getMethodName() + "(" + getClass().getName() + ")");
+ LOGGER.info("********************************************************************************");
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * commit REQUIRED
+ *
+ * have 1 rows
+ */
+ @Test
+ public void testRequired() throws Exception {
+ process.required(1);
+ checkCountRows(1);
+ }
+
+ /**
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * commit REQUIRES_NEW
+ *
+ * have 1 rows
+ */
+ @Test
+ public void testRequiresNew() throws Exception {
+ process.requiresNew(1);
+ checkCountRows(1);
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * roll back REQUIRED
+ *
+ * have 0 rows
+ */
+ @Test
+ public void testRequiredAndRollback() throws Exception {
+ try {
+ process.requiredAndRollback(1);
+ } catch(JtaRollbackException e) {
+ }
+ checkCountRows(0);
+ }
+
+ /**
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * roll back REQUIRES_NEW
+ *
+ * have 0 rows
+ */
+ @Test
+ public void testRequiresNewAndRollback() throws Exception {
+ try {
+ process.requiresNewAndRollback(1);
+ } catch(JtaRollbackException e) {
+ }
+ checkCountRows(0);
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * begin REQUIRES_NEW
+ * insert(id=2)
+ * commit REQUIRES_NEW
+ * commit REQUIRED
+ *
+ * have 2 rows
+ */
+ @Test
+ public void testRequiredAndRequiresNew() throws Exception {
+ process.requiredAndRequiresNew();
+ checkCountRows(2);
+ }
+
+ /**
+ * begin REQUIRED
+ * begin REQUIRES_NEW
+ * insert(id=2)
+ * commit REQUIRES_NEW
+ * insert(id=1)
+ * commit REQUIRED
+ *
+ * have 2 rows
+ */
+ @Test
+ public void testRequiresNewAndRequired() throws Exception {
+ process.requiresNewAndRequired();
+ checkCountRows(2);
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * begin REQUIRES_NEW
+ * insert(id=2)
+ * roll back REQUIRES_NEW
+ * commit REQUIRED
+ *
+ * have 1 rows and id=1 (from commited REQUIRED)
+ */
+ @Test
+ public void testRollbackInternalRequiresNew() throws Exception {
+ try {
+ process.rollbackInternalRequiresNew();
+ } catch(JtaRollbackException e) {
+ }
+ checkCountRowsAndIndex(1, 1);
+ }
+
+ /**
+ * begin REQUIRED
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * roll back REQUIRES_NEW
+ * insert(id=2)
+ * commit REQUIRED
+ *
+ * have 1 rows and id=2 (from commited REQUIRED)
+ */
+ @Test
+ public void testRollbackInternalRequiresNew2() throws Exception {
+ try {
+ process.rollbackInternalRequiresNew2();
+ } catch(JtaRollbackException e) {
+ }
+ checkCountRowsAndIndex(1, 2);
+ }
+
+
+ /**
+ * begin REQUIRED
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * commit REQUIRES_NEW
+ * insert(id=2)
+ * roll back REQUIRED
+ *
+ * have 1 rows and id=1 (from commited REQUIRES_NEW)
+ */
+ @Test
+ public void testRollbackExternalRequired() throws Exception {
+ try {
+ process.rollbackExternalRequired();
+ } catch(JtaRollbackException e) {
+ }
+ checkCountRowsAndIndex(1, 1);
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * begin REQUIRES_NEW
+ * insert(id=2)
+ * commit REQUIRES_NEW
+ * roll back REQUIRED
+ *
+ * have 1 rows and id=2 (from commited REQUIRES_NEW)
+ */
+ @Test
+ public void testRollbackExternalRequired2() throws Exception {
+ try {
+ process.rollbackExternalRequired2();
+ } catch(JtaRollbackException e) {
+ }
+ checkCountRowsAndIndex(1, 2);
+ }
+
+ private void checkCountRows(int count) throws Exception {
+ String name = testName.getMethodName();
+ List<Integer> readRows;
+ readRows = BaseDB.readRows(BaseDB.URL_DB1, BaseDB.NAME_DB1);
+ LOGGER.info("db1 check count rows {}:{}", count, readRows.size());
+
+ assertEquals(name + " db1 count rows", count, readRows.size());
+
+ readRows = BaseDB.readRows(BaseDB.URL_DB2, BaseDB.NAME_DB2);
+ LOGGER.info("db2 check count rows {}:{}", count, readRows.size());
+
+ assertEquals(name + " db2 count rows", count, readRows.size());
+ }
+
+ private void checkCountRowsAndIndex(int count, int index) throws Exception {
+ String name = testName.getMethodName();
+ List<Integer> readRows;
+ readRows = BaseDB.readRows(BaseDB.URL_DB1, BaseDB.NAME_DB1);
+
+ LOGGER.info("{} db1 check count rows {}:{}", new Object[]{name, count, readRows.size()});
+ LOGGER.info("{} db1 check row id {}:{}", new Object[]{name, index, readRows.get(0).intValue()});
+
+ assertEquals(name + " db1 count rows", count, readRows.size());
+ assertEquals(name + " db1 row id", index, readRows.get(0).intValue());
+
+ readRows = BaseDB.readRows(BaseDB.URL_DB2, BaseDB.NAME_DB2);
+ LOGGER.info("{} db2 check count rows {}:{}", new Object[]{name, count, readRows.size()});
+ LOGGER.info("{} db2 check row id {}:{}", new Object[]{name, index, readRows.get(0).intValue()});
+
+ assertEquals(name + " db2 count rows", count, readRows.size());
+ assertEquals(name + " db2 row id", index, readRows.get(0).intValue());
+ }
+}
diff --git a/src/test/java/org/mybatis/guice/jta/JtaMapper.java b/src/test/java/org/mybatis/guice/jta/JtaMapper.java
new file mode 100644
index 0000000..2024fd4
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/JtaMapper.java
@@ -0,0 +1,34 @@
+/*
+ * 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.jta;
+
+import java.util.List;
+
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Select;
+
+public interface JtaMapper {
+
+ @Insert ({
+ "insert into table1 (id, name) values (#{id}, #{name})"
+ })
+ int insertTable(TableRow row);
+
+ @Select ({
+ "select * from table1"
+ })
+ List<TableRow> selectAllTable();
+}
diff --git a/src/test/java/org/mybatis/guice/jta/JtaProcess.java b/src/test/java/org/mybatis/guice/jta/JtaProcess.java
new file mode 100644
index 0000000..71ab7b8
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/JtaProcess.java
@@ -0,0 +1,268 @@
+package org.mybatis.guice.jta;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.mybatis.guice.transactional.Transactional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JtaProcess {
+ private static final Logger LOGGER = LoggerFactory.getLogger(JtaLocalTest.class);
+
+ @Inject Provider<JtaProcess> provider;
+ @Inject JtaService1Impl db1;
+ @Inject JtaService2Impl db2;
+
+ public JtaProcess() {
+ }
+
+ private JtaProcess getProvider() {
+ JtaProcess process = provider.get();
+ return process;
+ }
+
+ private void inserts(int offset) {
+ LOGGER.info("inserts to db1");
+ inserts(db1, offset);
+ LOGGER.info("inserts to db2");
+ inserts(db2, offset);
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * commit REQUIRED
+ *
+ * have 1 rows
+ */
+ @Transactional
+ public void required(int offset) {
+ LOGGER.info("insert REQUIED transaction");
+ inserts(offset);
+ }
+
+ /**
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * commit REQUIRES_NEW
+ *
+ * have 1 rows
+ */
+ @Transactional(Transactional.TxType.REQUIRES_NEW)
+ public void requiresNew(int offset) {
+ LOGGER.info("insert REQUIES_NEW transaction");
+ inserts(offset);
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * roll back REQUIRED
+ *
+ * have 0 rows
+ */
+ @Transactional
+ public void requiredAndRollback(int offset) throws JtaRollbackException {
+ LOGGER.info("insert REQUIED transaction");
+ inserts(offset);
+ LOGGER.info("roll back REQUIED transaction");
+ throw new JtaRollbackException();
+ }
+
+ /**
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * roll back REQUIRES_NEW
+ *
+ * have 0 rows
+ */
+ @Transactional(Transactional.TxType.REQUIRES_NEW)
+ public void requiresNewAndRollback(int offset) throws JtaRollbackException {
+ LOGGER.info("insert REQUIES_NEW transaction");
+ inserts(offset);
+ LOGGER.info("roll back REQUIES_NEW transaction");
+ throw new JtaRollbackException();
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * begin REQUIRES_NEW
+ * insert(id=2)
+ * commit REQUIRES_NEW
+ * commit REQUIRED
+ *
+ * have 2 rows
+ */
+ @Transactional
+ public void requiredAndRequiresNew() {
+ JtaProcess process = getProvider();
+
+ LOGGER.info("insert REQUIED transaction");
+ process.inserts(1);
+ process.requiresNew(2);
+ }
+
+ /**
+ * begin REQUIRED
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * commit REQUIRES_NEW
+ * insert(id=2)
+ * commit REQUIRED
+ *
+ * have 2 rows
+ */
+ @Transactional
+ public void requiresNewAndRequired() {
+ JtaProcess process = getProvider();
+
+ process.requiresNew(1);
+ LOGGER.info("insert REQUIED transaction");
+ process.inserts(2);
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * begin REQUIRES_NEW
+ * insert(id=2)
+ * roll back REQUIRES_NEW
+ * commit REQUIRED
+ *
+ * have 1 rows and id=1 (from commited REQUIRED)
+ */
+ @Transactional
+ public void rollbackInternalRequiresNew() throws JtaRollbackException {
+ JtaProcess process = getProvider();
+
+ LOGGER.info("transaction process:"
+ + "\n\t* begin REQUIRED"
+ + "\n\t* insert(id=1)"
+ + "\n\t* begin REQUIRES_NEW"
+ + "\n\t* insert(id=2)"
+ + "\n\t* roll back REQUIRES_NEW"
+ + "\n\t* commit REQUIRED"
+ + "\n\t*"
+ + "\n\t* have 1 rows and id=1 (from commited REQUIRED)");
+
+ LOGGER.info("insert REQUIED transaction");
+ process.inserts(1);
+
+ try {
+ process.requiresNewAndRollback(2);
+ } catch(JtaRollbackException e) {
+ }
+ }
+
+ /**
+ * begin REQUIRED
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * roll back REQUIRES_NEW
+ * insert(id=2)
+ * commit REQUIRED
+ *
+ * have 1 rows and id=2 (from commited REQUIRED)
+ */
+ @Transactional
+ public void rollbackInternalRequiresNew2() throws JtaRollbackException {
+ JtaProcess process = getProvider();
+
+ LOGGER.info("transaction process:"
+ + "\n\t* begin REQUIRED"
+ + "\n\t* begin REQUIRES_NEW"
+ + "\n\t* insert(id=1)"
+ + "\n\t* roll back REQUIRES_NEW"
+ + "\n\t* insert(id=2)"
+ + "\n\t* commit REQUIRED"
+ + "\n\t*"
+ + "\n\t* have 1 rows and id=2 (from commited REQUIRED)");
+
+ try {
+ process.requiresNewAndRollback(1);
+ } catch(JtaRollbackException e) {
+ }
+
+ LOGGER.info("insert REQUIED transaction");
+ process.inserts(2);
+ }
+
+ /**
+ * begin REQUIRED
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * commit REQUIRES_NEW
+ * insert(id=2)
+ * roll back REQUIRED
+ *
+ * have 1 rows and id=1 (from commited REQUIRES_NEW)
+ */
+ @Transactional
+ public void rollbackExternalRequired() throws JtaRollbackException {
+ JtaProcess process = getProvider();
+
+ LOGGER.info("transaction process:"
+ + "\n\t* begin REQUIRED"
+ + "\n\t* begin REQUIRES_NEW"
+ + "\n\t* insert(id=1)"
+ + "\n\t* commit REQUIRES_NEW"
+ + "\n\t* insert(id=2)"
+ + "\n\t* roll back REQUIRED"
+ + "\n\t* "
+ + "\n\t* have 1 rows and id=1 (from commited REQUIRES_NEW)");
+
+ process.requiresNew(1);
+ LOGGER.info("insert REQUIED transaction");
+ process.inserts(2);
+ LOGGER.info("roll back REQUIED transaction");
+ throw new JtaRollbackException();
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * begin REQUIRES_NEW
+ * insert(id=2)
+ * commit REQUIRES_NEW
+ * roll back REQUIRED
+ *
+ * have 1 rows and id=2 (from commited REQUIRES_NEW)
+ */
+ @Transactional
+ public void rollbackExternalRequired2() throws JtaRollbackException {
+ JtaProcess process = getProvider();
+
+ LOGGER.info("transaction process:"
+ + "\n\t* begin REQUIRED"
+ + "\n\t* insert(id=1)"
+ + "\n\t* begin REQUIRES_NEW"
+ + "\n\t* insert(id=2)"
+ + "\n\t* commit REQUIRES_NEW"
+ + "\n\t* roll back REQUIRED"
+ + "\n\t*"
+ + "\n\t* have 1 rows and id=2 (from commited REQUIRES_NEW)");
+
+ LOGGER.info("insert REQUIED transaction");
+ process.inserts(1);
+ process.requiresNew(2);
+ LOGGER.info("roll back REQUIED transaction after commit REQUIRES_NEW transaction");
+ throw new JtaRollbackException();
+ }
+
+
+ private void inserts(JtaService1Impl service, int offset) {
+ TableRow tr = new TableRow();
+ tr.setId(offset);
+ tr.setName("name " + offset);
+ service.insertTable(tr);
+ }
+
+ private void inserts(JtaService2Impl service, int offset) {
+ TableRow tr = new TableRow();
+ tr.setId(offset);
+ tr.setName("name " + offset);
+ service.insertTable(tr);
+ }
+}
diff --git a/src/test/java/org/mybatis/guice/jta/JtaRollbackException.java b/src/test/java/org/mybatis/guice/jta/JtaRollbackException.java
new file mode 100644
index 0000000..cc0500d
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/JtaRollbackException.java
@@ -0,0 +1,8 @@
+package org.mybatis.guice.jta;
+
+public class JtaRollbackException extends Exception {
+ private static final long serialVersionUID = -4005752017823397888L;
+
+ public JtaRollbackException() {
+ }
+}
diff --git a/src/test/java/org/mybatis/guice/jta/JtaService.java b/src/test/java/org/mybatis/guice/jta/JtaService.java
new file mode 100644
index 0000000..f371662
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/JtaService.java
@@ -0,0 +1,11 @@
+package org.mybatis.guice.jta;
+
+import java.util.List;
+
+public interface JtaService {
+ int insertTable(TableRow row);
+
+ List<TableRow> selectAllTable();
+
+ String getName();
+}
diff --git a/src/test/java/org/mybatis/guice/jta/JtaService1Impl.java b/src/test/java/org/mybatis/guice/jta/JtaService1Impl.java
new file mode 100644
index 0000000..2732d6c
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/JtaService1Impl.java
@@ -0,0 +1,36 @@
+package org.mybatis.guice.jta;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.guice.transactional.Transactional;
+
+public class JtaService1Impl {
+ private final Log log = LogFactory.getLog(getClass());
+
+ @Inject JtaMapper mapper;
+ @Inject SqlSessionFactory factory;
+
+ public JtaService1Impl() {
+ }
+
+ @Transactional
+ public int insertTable(TableRow row) {
+ log.debug(getName() + " insertTable");
+ return mapper.insertTable(row);
+ }
+
+ @Transactional
+ public List<TableRow> selectAllTable() {
+ log.debug(getName() + " selectAllTable");
+ return mapper.selectAllTable();
+ }
+
+ public String getName() {
+ return factory.getConfiguration().getEnvironment().getId();
+ }
+}
diff --git a/src/test/java/org/mybatis/guice/jta/JtaService2Impl.java b/src/test/java/org/mybatis/guice/jta/JtaService2Impl.java
new file mode 100644
index 0000000..7b26bff
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/JtaService2Impl.java
@@ -0,0 +1,36 @@
+package org.mybatis.guice.jta;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.guice.transactional.Transactional;
+
+public class JtaService2Impl {
+ private final Log log = LogFactory.getLog(getClass());
+
+ @Inject JtaMapper mapper;
+ @Inject SqlSessionFactory factory;
+
+ public JtaService2Impl() {
+ }
+
+ @Transactional
+ public int insertTable(TableRow row) {
+ log.debug(getName() + " insertTable");
+ return mapper.insertTable(row);
+ }
+
+ @Transactional
+ public List<TableRow> selectAllTable() {
+ log.debug(getName() + " selectAllTable");
+ return mapper.selectAllTable();
+ }
+
+ public String getName() {
+ return factory.getConfiguration().getEnvironment().getId();
+ }
+}
diff --git a/src/test/java/org/mybatis/guice/jta/JtaXaRollbackTest.java b/src/test/java/org/mybatis/guice/jta/JtaXaRollbackTest.java
new file mode 100644
index 0000000..b08f6e0
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/JtaXaRollbackTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.jta;
+
+import static org.junit.Assert.assertEquals;
+
+import java.sql.Connection;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.aries.transaction.AriesTransactionManager;
+import org.apache.aries.transaction.internal.AriesTransactionManagerImpl;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mybatis.guice.transactional.TransactionAttribute;
+import org.mybatis.guice.transactional.TransactionToken;
+
+/**
+ * Create Requerd transaction. Create internal RequiresNew transaction. Rollback first transaction.
+ * Warning: transaction will roll back. XA error code: 100
+ */
+public class JtaXaRollbackTest {
+
+ private static DataSource dataSource;
+ private static AriesTransactionManager manager;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
+
+ manager = new AriesTransactionManagerImpl();
+
+ String className = "org.apache.derby.jdbc.EmbeddedDriver";
+ Class.forName(className).newInstance();
+
+ dataSource = BaseDB.createXADataSource(BaseDB.NAME_DB1, BaseDB.URL_DB1, manager);
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ BaseDB.dropTable(BaseDB.URL_DB1);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ BaseDB.clearTable(BaseDB.URL_DB1);
+ }
+
+ @Test
+ public void testFirstRollback() throws Exception {
+ TransactionAttribute firstAttribute = TransactionAttribute.REQUIRED;
+ TransactionAttribute secondAttribute = TransactionAttribute.REQUIRESNEW;
+
+ // REQUIRED transaction
+ TransactionToken firstToken = firstAttribute.begin(manager);
+ try {
+ Connection firstCon = dataSource.getConnection();
+ BaseDB.insertRow(firstCon, 1, "name 1");
+ firstCon.close();
+
+ // REQUIRESNEW transaction
+ TransactionToken secondToken = secondAttribute.begin(manager);
+ try {
+ Connection secondCon = dataSource.getConnection();
+ BaseDB.insertRow(secondCon, 2, "name 2");
+ secondCon.close();
+ } finally {
+ secondAttribute.finish(manager, secondToken);
+ }
+
+ // roll back REQUIRED after commit REQUIRESNEW
+ throw new Exception("rollback");
+
+ } catch(Exception e) {
+ manager.setRollbackOnly();
+ } finally {
+ firstAttribute.finish(manager, firstToken);
+ }
+
+ List<Integer> rows = BaseDB.readRows(BaseDB.URL_DB1, BaseDB.NAME_DB1);
+ assertEquals(1, rows.size());
+ assertEquals(2, rows.get(0).intValue());
+ }
+
+ @Test
+ public void testSecondRollback() throws Exception {
+ TransactionAttribute firstAttribute = TransactionAttribute.REQUIRED;
+ TransactionAttribute secondAttribute = TransactionAttribute.REQUIRESNEW;
+
+ // REQUIRED transaction
+ TransactionToken firstToken = firstAttribute.begin(manager);
+ try {
+ Connection firstCon = dataSource.getConnection();
+ BaseDB.insertRow(firstCon, 1, "name 1");
+ firstCon.close();
+
+ // REQUIRESNEW transaction
+ TransactionToken secondToken = secondAttribute.begin(manager);
+ try {
+ Connection secondCon = dataSource.getConnection();
+ BaseDB.insertRow(secondCon, 2, "name 2");
+ secondCon.close();
+
+ // roll back REQUIRESNEW and commit REQUIRED
+ throw new Exception("rollback");
+ } catch(Exception e) {
+ // not throws exception to REQUITED
+ manager.setRollbackOnly();
+ } finally {
+ secondAttribute.finish(manager, secondToken);
+ }
+
+ } catch(Exception e) {
+ manager.setRollbackOnly();
+ } finally {
+ firstAttribute.finish(manager, firstToken);
+ }
+
+ List<Integer> rows = BaseDB.readRows(BaseDB.URL_DB1, BaseDB.NAME_DB1);
+ assertEquals(1, rows.size());
+ assertEquals(1, rows.get(0).intValue());
+ }
+}
diff --git a/src/test/java/org/mybatis/guice/jta/JtaXaTest.java b/src/test/java/org/mybatis/guice/jta/JtaXaTest.java
new file mode 100644
index 0000000..70f8b67
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/JtaXaTest.java
@@ -0,0 +1,328 @@
+package org.mybatis.guice.jta;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.apache.aries.transaction.AriesTransactionManager;
+import org.apache.aries.transaction.internal.AriesTransactionManagerImpl;
+import org.apache.ibatis.logging.LogFactory;
+import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.mybatis.guice.MyBatisJtaModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.PrivateModule;
+
+public class JtaXaTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(JtaXaTest.class);
+
+ static AriesTransactionManager manager;
+ static DataSource dataSource1;
+ static DataSource dataSource2;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
+
+ manager = new AriesTransactionManagerImpl();
+
+ dataSource1 = BaseDB.createXADataSource(BaseDB.NAME_DB1, BaseDB.URL_DB1, manager);
+ dataSource2 = BaseDB.createXADataSource(BaseDB.NAME_DB2, BaseDB.URL_DB2, manager);
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ BaseDB.dropTable(BaseDB.URL_DB1);
+ BaseDB.dropTable(BaseDB.URL_DB2);
+ }
+
+ @Rule
+ public TestName testName = new TestName();
+ private Injector injector;
+
+ JtaProcess process;
+
+ @Before
+ public void setup() throws Exception {
+ LOGGER.info("********************************************************************************");
+ LOGGER.info("Testing: " + testName.getMethodName() + "(" + getClass().getName() + ")");
+ LOGGER.info("********************************************************************************");
+ LogFactory.useSlf4jLogging();
+
+
+ LOGGER.info("create injector");
+ injector = Guice.createInjector(
+ new PrivateModule() {
+
+ @Override
+ protected void configure() {
+ install(new MyBatisJtaModule(manager) {
+
+ @Override
+ protected void initialize() {
+ environmentId("db1");
+ bindDataSourceProvider(new ProviderImpl<DataSource>(dataSource1));
+ bindDefaultTransactionProvider();
+ bindDatabaseIdProvider(new VendorDatabaseIdProvider());
+
+ addMapperClass(JtaMapper.class);
+
+ bind(JtaService1Impl.class);
+ }
+ });
+
+ expose(JtaService1Impl.class);
+ };
+ },
+ new PrivateModule() {
+
+ @Override
+ protected void configure() {
+ install(new MyBatisJtaModule(manager) {
+
+ @Override
+ protected void initialize() {
+ environmentId("db2");
+ bindDataSourceProvider(new ProviderImpl<DataSource>(dataSource2));
+ bindDefaultTransactionProvider();
+ bindDatabaseIdProvider(new VendorDatabaseIdProvider());
+
+ addMapperClass(JtaMapper.class);
+
+ bind(JtaService2Impl.class);
+ bind(JtaProcess.class);
+ }
+ });
+
+ expose(JtaService2Impl.class);
+ expose(JtaProcess.class);
+ };
+ }
+ );
+
+ injector.injectMembers(this);
+ process = injector.getInstance(JtaProcess.class);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ BaseDB.clearTable(BaseDB.URL_DB1);
+ BaseDB.clearTable(BaseDB.URL_DB2);
+
+ LOGGER.info("********************************************************************************");
+ LOGGER.info("Testing done: " + testName.getMethodName() + "(" + getClass().getName() + ")");
+ LOGGER.info("********************************************************************************");
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * commit REQUIRED
+ *
+ * have 1 rows
+ */
+ @Test
+ public void testRequired() throws Exception {
+ process.required(1);
+ checkCountRows(1);
+ }
+
+ /**
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * commit REQUIRES_NEW
+ *
+ * have 1 rows
+ */
+ @Test
+ public void testRequiresNew() throws Exception {
+ process.requiresNew(1);
+ checkCountRows(1);
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * roll back REQUIRED
+ *
+ * have 0 rows
+ */
+ @Test
+ public void testRequiredAndRollback() throws Exception {
+ try {
+ process.requiredAndRollback(1);
+ } catch(JtaRollbackException e) {
+ }
+ checkCountRows(0);
+ }
+
+ /**
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * roll back REQUIRES_NEW
+ *
+ * have 0 rows
+ */
+ @Test
+ public void testRequiresNewAndRollback() throws Exception {
+ try {
+ process.requiresNewAndRollback(1);
+ } catch(JtaRollbackException e) {
+ }
+ checkCountRows(0);
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * begin REQUIRES_NEW
+ * insert(id=2)
+ * commit REQUIRES_NEW
+ * commit REQUIRED
+ *
+ * have 2 rows
+ */
+ @Test
+ public void testRequiredAndRequiresNew() throws Exception {
+ process.requiredAndRequiresNew();
+ checkCountRows(2);
+ }
+
+ /**
+ * begin REQUIRED
+ * begin REQUIRES_NEW
+ * insert(id=2)
+ * commit REQUIRES_NEW
+ * insert(id=1)
+ * commit REQUIRED
+ *
+ * have 2 rows
+ */
+ @Test
+ public void testRequiresNewAndRequired() throws Exception {
+ process.requiresNewAndRequired();
+ checkCountRows(2);
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * begin REQUIRES_NEW
+ * insert(id=2)
+ * roll back REQUIRES_NEW
+ * commit REQUIRED
+ *
+ * have 1 rows and id=1 (from commited REQUIRED)
+ */
+ @Test
+ public void testRollbackInternalRequiresNew() throws Exception {
+ try {
+ process.rollbackInternalRequiresNew();
+ } catch(JtaRollbackException e) {
+ }
+ checkCountRowsAndIndex(1, 1);
+ }
+
+ /**
+ * begin REQUIRED
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * roll back REQUIRES_NEW
+ * insert(id=2)
+ * commit REQUIRED
+ *
+ * have 1 rows and id=2 (from commited REQUIRED)
+ */
+ @Test
+ public void testRollbackInternalRequiresNew2() throws Exception {
+ try {
+ process.rollbackInternalRequiresNew2();
+ } catch(JtaRollbackException e) {
+ }
+ checkCountRowsAndIndex(1, 2);
+ }
+
+
+ /**
+ * begin REQUIRED
+ * begin REQUIRES_NEW
+ * insert(id=1)
+ * commit REQUIRES_NEW
+ * insert(id=2)
+ * roll back REQUIRED
+ *
+ * have 1 rows and id=1 (from commited REQUIRES_NEW)
+ */
+ @Test
+ public void testRollbackExternalRequired() throws Exception {
+ try {
+ process.rollbackExternalRequired();
+ } catch(JtaRollbackException e) {
+ }
+ checkCountRowsAndIndex(1, 1);
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * begin REQUIRES_NEW
+ * insert(id=2)
+ * commit REQUIRES_NEW
+ * roll back REQUIRED
+ *
+ * have 1 rows and id=2 (from commited REQUIRES_NEW)
+ */
+ @Test
+ public void testRollbackExternalRequired2() throws Exception {
+ try {
+ process.rollbackExternalRequired2();
+ } catch(JtaRollbackException e) {
+ }
+ checkCountRowsAndIndex(1, 2);
+ }
+
+ private void checkCountRows(int count) throws Exception {
+ String name = testName.getMethodName();
+ List<Integer> readRows;
+ readRows = BaseDB.readRows(BaseDB.URL_DB1, BaseDB.NAME_DB1);
+ LOGGER.info("db1 check count rows {}:{}", count, readRows.size());
+
+ assertEquals(name + " db1 count rows", count, readRows.size());
+
+ readRows = BaseDB.readRows(BaseDB.URL_DB2, BaseDB.NAME_DB2);
+ LOGGER.info("db2 check count rows {}:{}", count, readRows.size());
+
+ assertEquals(name + " db2 count rows", count, readRows.size());
+ }
+
+ private void checkCountRowsAndIndex(int count, int index) throws Exception {
+ String name = testName.getMethodName();
+ List<Integer> readRows;
+ readRows = BaseDB.readRows(BaseDB.URL_DB1, BaseDB.NAME_DB1);
+
+ LOGGER.info("{} db1 check count rows {}:{}", new Object[]{name, count, readRows.size()});
+ LOGGER.info("{} db1 check row id {}:{}", new Object[]{name, index, readRows.get(0).intValue()});
+
+ assertEquals(name + " db1 count rows", count, readRows.size());
+ assertEquals(name + " db1 row id", index, readRows.get(0).intValue());
+
+ readRows = BaseDB.readRows(BaseDB.URL_DB2, BaseDB.NAME_DB2);
+ LOGGER.info("{} db2 check count rows {}:{}", new Object[]{name, count, readRows.size()});
+ LOGGER.info("{} db2 check row id {}:{}", new Object[]{name, index, readRows.get(0).intValue()});
+
+ assertEquals(name + " db2 count rows", count, readRows.size());
+ assertEquals(name + " db2 row id", index, readRows.get(0).intValue());
+ }
+}
diff --git a/src/test/java/org/mybatis/guice/jta/TableRow.java b/src/test/java/org/mybatis/guice/jta/TableRow.java
new file mode 100644
index 0000000..3b0ed1c
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/TableRow.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010-2015 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.jta;
+
+public class TableRow {
+ private int id;
+ private String name;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/src/test/resources/org/mybatis/guice/jta/setupdb.sql b/src/test/resources/org/mybatis/guice/jta/setupdb.sql
new file mode 100644
index 0000000..6decc85
--- /dev/null
+++ b/src/test/resources/org/mybatis/guice/jta/setupdb.sql
@@ -0,0 +1,30 @@
+-- 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.
+
+-- version: $Id$
+
+--drop table if exists table1;
+--drop table if exists table2;
+
+create table table1 (
+ id integer not null,
+ name varchar(80) not null,
+ constraint pk_table1 primary key (id)
+);
+
+create table table2 (
+ id integer not null,
+ name varchar(80) not null,
+ constraint pk_table2 primary key (id)
+);
diff --git a/src/test/resources/org/mybatis/guice/jta/shutdowndb.sql b/src/test/resources/org/mybatis/guice/jta/shutdowndb.sql
new file mode 100644
index 0000000..8ebd443
--- /dev/null
+++ b/src/test/resources/org/mybatis/guice/jta/shutdowndb.sql
@@ -0,0 +1,21 @@
+-- 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.
+
+-- version: $Id$
+
+-- drop table table1;
+-- drop table table2;
+
+delete from table1;
+delete from table2;
--
1.9.5.github.0
From d9def223b45d4c6c42eafe042ee3d63bb3fdeae5 Mon Sep 17 00:00:00 2001
From: Aleksey Sushko <alexey.l.sushko@gmail.com>
Date: Thu, 12 Mar 2015 00:31:53 +0300
Subject: [PATCH 2/6] request #31 NPE on java.lang.Object methods invocation of
a service annotated with @Transactional
---
.../org/mybatis/guice/AbstractMyBatisModule.java | 2 +-
.../java/org/mybatis/guice/MyBatisJtaModule.java | 100 +++++++-------
.../TxTransactionalMethodInterceptor.java | 148 ++++++++++-----------
3 files changed, 125 insertions(+), 125 deletions(-)
diff --git a/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java b/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
index f59f867..cb894e7 100644
--- a/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
+++ b/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
@@ -40,7 +40,7 @@ import static com.google.inject.util.Providers.guicify;
*/
abstract class AbstractMyBatisModule extends AbstractModule {
- private static final AbstractMatcher<Method> DECLARED_BY_OBJECT = new AbstractMatcher<Method>() {
+ protected static final AbstractMatcher<Method> DECLARED_BY_OBJECT = new AbstractMatcher<Method>() {
public boolean matches(Method method) {
return method.getDeclaringClass() == Object.class;
}
diff --git a/src/main/java/org/mybatis/guice/MyBatisJtaModule.java b/src/main/java/org/mybatis/guice/MyBatisJtaModule.java
index 245b051..f22db5f 100644
--- a/src/main/java/org/mybatis/guice/MyBatisJtaModule.java
+++ b/src/main/java/org/mybatis/guice/MyBatisJtaModule.java
@@ -17,70 +17,70 @@ import org.mybatis.guice.transactional.TransactionalMethodInterceptor;
import org.mybatis.guice.transactional.TxTransactionalMethodInterceptor;
public abstract class MyBatisJtaModule extends MyBatisModule {
- private final Log log = LogFactory.getLog(getClass());
+ private final Log log = LogFactory.getLog(getClass());
- private TransactionManager transactionManager;
+ private TransactionManager transactionManager;
- public MyBatisJtaModule() {
- }
+ public MyBatisJtaModule() {
+ }
- public MyBatisJtaModule(TransactionManager transactionManager) {
- this.transactionManager = transactionManager;
- }
+ public MyBatisJtaModule(TransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
- @Override
- protected void bindTransactionInterceptors() {
- TransactionManager manager = getTransactionManager();
+ @Override
+ protected void bindTransactionInterceptors() {
+ TransactionManager manager = getTransactionManager();
- if (manager == null) {
- log.debug("bind default transaction interceptors");
- super.bindTransactionInterceptors();
- } else {
- log.debug("bind XA transaction interceptors");
+ if (manager == null) {
+ log.debug("bind default transaction interceptors");
+ super.bindTransactionInterceptors();
+ } else {
+ log.debug("bind XA transaction interceptors");
- // mybatis transactional interceptor
- TransactionalMethodInterceptor interceptor = new TransactionalMethodInterceptor();
- requestInjection(interceptor);
+ // transactional interceptor
+ TransactionalMethodInterceptor interceptor = new TransactionalMethodInterceptor();
+ requestInjection(interceptor);
- // jta transactional interceptor
- TxTransactionalMethodInterceptor interceptorTx = new TxTransactionalMethodInterceptor();
- requestInjection(interceptorTx);
+ // 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);
- }
- }
+ bind(TransactionManager.class).toInstance(manager);
- protected TransactionManager getTransactionManager() {
- return transactionManager;
- }
+ bindInterceptor(any(), not(DECLARED_BY_OBJECT).and(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(DECLARED_BY_OBJECT).and(not(annotatedWith(Transactional.class))), interceptorTx, interceptor);
+ }
+ }
- protected void setTransactionManager(TransactionManager transactionManager) {
- this.transactionManager = transactionManager;
- }
+ protected TransactionManager getTransactionManager() {
+ return transactionManager;
+ }
- protected void bindDefaultTransactionProvider() {
- Class<? extends TransactionFactory> factoryType = getTransactionManager() == null ?
- JdbcTransactionFactory.class : ManagedTransactionFactory.class;
+ protected void setTransactionManager(TransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
- bindTransactionFactoryType(factoryType);
- }
+ protected void bindDefaultTransactionProvider() {
+ Class<? extends TransactionFactory> factoryType = getTransactionManager() == null ?
+ JdbcTransactionFactory.class : ManagedTransactionFactory.class;
- protected static class ProviderImpl<T> implements Provider<T> {
- private T wrapper;
+ bindTransactionFactoryType(factoryType);
+ }
- public ProviderImpl(T wrapper) {
- this.wrapper = wrapper;
- }
+ protected static class ProviderImpl<T> implements Provider<T> {
+ private T wrapper;
- public T get() {
- return wrapper;
- }
+ public ProviderImpl(T wrapper) {
+ this.wrapper = wrapper;
+ }
- }
+ public T get() {
+ return wrapper;
+ }
+
+ }
}
diff --git a/src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java b/src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java
index 643135e..6ec91a3 100644
--- a/src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java
+++ b/src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java
@@ -42,8 +42,8 @@ public class TxTransactionalMethodInterceptor implements MethodInterceptor {
@Inject private TransactionManager manager;
@Inject private SqlSessionManager sqlSessionManager;
- public TxTransactionalMethodInterceptor() {
- }
+ public TxTransactionalMethodInterceptor() {
+ }
/**
* {@inheritDoc}
@@ -64,81 +64,81 @@ public class TxTransactionalMethodInterceptor implements MethodInterceptor {
boolean needsRollback = transactional.rollbackOnly();
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));
- }
+ TransactionAttribute attribute = null;
- // 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()));
- }
+ 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;
+ }
- // with Tx
- TransactionToken tranToken = attribute.begin(manager);
-
- log.debug("enlistResource XASqlSessionManager");
- XASqlSessionManager xaRes = new XASqlSessionManager(sqlSessionManager);
- tranToken.getActiveTransaction().enlistResource(xaRes);
+ if(attribute == null) {
+ if(log.isDebugEnabled()) {
+ log.debug(format("%s - skip Tx Transaction", debugPrefix));
+ }
- try {
- if(log.isDebugEnabled()) {
- log.debug(format("%s - Tx Transaction %s (CompletionAllowed %s) call method",
- debugPrefix,
- attribute.name(),
- tranToken.isCompletionAllowed()));
- }
- object = invocation.proceed();
+ // 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()));
+ }
- if(needsRollback)
- manager.setRollbackOnly();
-
- } 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;
- }
+ // with Tx
+ TransactionToken tranToken = attribute.begin(manager);
+
+ log.debug("enlistResource XASqlSessionManager");
+ XASqlSessionManager xaRes = new XASqlSessionManager(sqlSessionManager);
+ tranToken.getActiveTransaction().enlistResource(xaRes);
+
+ try {
+ if(log.isDebugEnabled()) {
+ log.debug(format("%s - Tx Transaction %s (CompletionAllowed %s) call method",
+ debugPrefix,
+ attribute.name(),
+ tranToken.isCompletionAllowed()));
+ }
+ object = invocation.proceed();
+
+ if(needsRollback)
+ manager.setRollbackOnly();
+
+ } 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;
+ }
}
--
1.9.5.github.0
From 4fcd224f47e0ffe9bd531635df53788982626521 Mon Sep 17 00:00:00 2001
From: Aleksey Sushko <alexey.l.sushko@gmail.com>
Date: Thu, 12 Mar 2015 00:32:40 +0300
Subject: [PATCH 3/6] #29 check XA status
---
.../guice/transactional/MyBatisXAException.java | 19 +
.../guice/transactional/XASqlSessionManager.java | 684 +++++++++++++--------
2 files changed, 435 insertions(+), 268 deletions(-)
create mode 100644 src/main/java/org/mybatis/guice/transactional/MyBatisXAException.java
diff --git a/src/main/java/org/mybatis/guice/transactional/MyBatisXAException.java b/src/main/java/org/mybatis/guice/transactional/MyBatisXAException.java
new file mode 100644
index 0000000..c905bab
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/transactional/MyBatisXAException.java
@@ -0,0 +1,19 @@
+package org.mybatis.guice.transactional;
+
+import javax.transaction.xa.XAException;
+
+public class MyBatisXAException extends XAException {
+ private static final long serialVersionUID = -7280133560046132709L;
+
+ public MyBatisXAException(String message, int errorCode) {
+ super(message);
+ this.errorCode = errorCode;
+ }
+
+ public MyBatisXAException(String message, int errorCode, Throwable t) {
+ super(message);
+ this.errorCode = errorCode;
+ initCause(t);
+ }
+
+}
diff --git a/src/main/java/org/mybatis/guice/transactional/XASqlSessionManager.java b/src/main/java/org/mybatis/guice/transactional/XASqlSessionManager.java
index 13a97fe..5b9642a 100644
--- a/src/main/java/org/mybatis/guice/transactional/XASqlSessionManager.java
+++ b/src/main/java/org/mybatis/guice/transactional/XASqlSessionManager.java
@@ -33,323 +33,471 @@ import org.apache.ibatis.session.SqlSessionManager;
public class XASqlSessionManager implements XAResource, NamedXAResource {
private static final Log log = LogFactory.getLog(XASqlSessionManager.class);
+ public static final int NO_TX = 0;
+ public static final int STARTED = 1;
+ public static final int ENDED = 2;
+ public static final int PREPARED = 3;
+
private SqlSessionManager sqlSessionManager;
- private int transactionTimeout;
- private String id;
+ private int transactionTimeout;
+ private String id;
+ private Xid xid;
+ private int state = NO_TX;
- private static HashMap<GlobalKey, GlobalToken> globalTokens = new HashMap<XASqlSessionManager.GlobalKey, XASqlSessionManager.GlobalToken>();
+ private static HashMap<GlobalKey, GlobalToken> globalTokens = new HashMap<XASqlSessionManager.GlobalKey, XASqlSessionManager.GlobalToken>();
- public XASqlSessionManager(SqlSessionManager sqlSessionManager) {
- this.sqlSessionManager = sqlSessionManager;
- id = sqlSessionManager.getConfiguration().getEnvironment().getId();
- }
+ public XASqlSessionManager(SqlSessionManager sqlSessionManager) {
+ this.sqlSessionManager = sqlSessionManager;
+ id = sqlSessionManager.getConfiguration().getEnvironment().getId();
+ }
- //@Override
- public String getName() {
- return id;
- }
+ //@Override
+ public String getName() {
+ return id;
+ }
- //@Override
- public void commit(Xid xid, boolean flag) throws XAException {
- if(log.isDebugEnabled()) {
- log.debug(id + " commit flag:" + flag + " " + xid);
- }
- parentResume(xid);
- }
+ public int getState() {
+ return state;
+ }
- //@Override
- public void end(Xid xid, int flag) throws XAException {
- if(log.isDebugEnabled()) {
- log.debug(id + " end flag:" + printFlag(flag) + " " + xid);
- }
+ private String xlatedState() {
+ switch (state) {
+ case NO_TX: return "NO_TX";
+ case STARTED: return "STARTED";
+ case ENDED: return "ENDED";
+ case PREPARED: return "PREPARED";
+ default: return "!invalid state (" + state + ")!";
+ }
+ }
- // TODO XAResource.TMSUSPEND ?
- }
+ private String decodeXAResourceFlag(int flag) {
+ switch(flag) {
+ case XAResource.TMENDRSCAN: return "TMENDRSCAN";
+ case XAResource.TMFAIL: return "TMFAIL";
+ case XAResource.TMJOIN: return "TMJOIN";
+ case XAResource.TMNOFLAGS: return "TMNOFLAGS";
+ case XAResource.TMONEPHASE: return "TMONEPHASE";
+ case XAResource.TMRESUME: return "TMRESUME";
+ case XAResource.TMSTARTRSCAN: return "TMSTARTRSCAN";
+ case XAResource.TMSUCCESS: return "TMSUCCESS";
+ case XAResource.TMSUSPEND: return "TMSUSPEND";
+ default: return "" + flag;
+ }
+ }
- //@Override
- public void forget(Xid xid) throws XAException {
- if(log.isDebugEnabled()) {
- log.debug(id + " forget " + xid);
- }
- }
+ //@Override
+ public int getTransactionTimeout() throws XAException {
+ return transactionTimeout;
+ }
- //@Override
- public int getTransactionTimeout() throws XAException {
- return transactionTimeout;
- }
+ //@Override
+ public boolean setTransactionTimeout(int second) throws XAException {
+ transactionTimeout = second;
+ return true;
+ }
- //@Override
- public boolean isSameRM(XAResource xares) throws XAException {
- return this == xares;
- }
+ //@Override
+ public void forget(Xid xid) throws XAException {
+ }
- //@Override
- public int prepare(Xid xid) throws XAException {
- if(log.isDebugEnabled()) {
- log.debug(id + " prepare " + xid);
- }
- return 0;
- }
+ //@Override
+ public Xid[] recover(int flags) throws XAException {
+ return new Xid[0];
+ }
- //@Override
- public Xid[] recover(int flag) throws XAException {
- if(log.isDebugEnabled()) {
- log.debug(id + " recover flag:" + printFlag(flag));
- }
+ //@Override
+ public boolean isSameRM(XAResource xares) throws XAException {
+ return this == xares;
+ }
- // TODO return empty array or null? or current xid?
- return new Xid[0];
- }
+ //@Override
+ public void start(Xid xid, int flag) throws XAException {
+ if(log.isDebugEnabled()) log.debug(id + ": call start old state=" + xlatedState() + ", XID=" + xid + ", flag=" + decodeXAResourceFlag(flag));
- //@Override
- public void rollback(Xid xid) throws XAException {
- if(log.isDebugEnabled()) {
- log.debug(id + " rollback " + xid);
- }
+ if (flag != XAResource.TMNOFLAGS && flag != XAResource.TMJOIN)
+ throw new MyBatisXAException(id + ": unsupported start flag " + decodeXAResourceFlag(flag), XAException.XAER_RMERR);
+ if (xid == null)
+ throw new MyBatisXAException(id + ": XID cannot be null", XAException.XAER_INVAL);
- parentResume(xid);
- }
+ if (state == NO_TX) {
+ if (this.xid != null)
+ throw new MyBatisXAException(id + ": resource already started on XID " + this.xid, XAException.XAER_PROTO);
+ else {
+ if (flag == XAResource.TMJOIN)
+ throw new MyBatisXAException(id + ": resource not yet started", XAException.XAER_PROTO);
+ else {
+ if (log.isDebugEnabled()) log.debug(id + ": OK to start, old state=" + xlatedState() + ", XID=" + xid + ", flag=" + decodeXAResourceFlag(flag));
+ this.xid = xid;
+ }
+ }
+ }
+ else if (state == STARTED) {
+ throw new MyBatisXAException(id + ": resource already started on XID " + this.xid, XAException.XAER_PROTO);
+ }
+ else if (state == ENDED) {
+ if (flag == XAResource.TMNOFLAGS)
+ throw new MyBatisXAException(id + ": resource already registered XID " + this.xid, XAException.XAER_DUPID);
+ else {
+ if (xid.equals(this.xid)) {
+ if (log.isDebugEnabled()) log.debug(id + ": OK to join, old state=" + xlatedState() + ", XID=" + xid + ", flag=" + decodeXAResourceFlag(flag));
+ }
+ else
+ throw new MyBatisXAException(id + ": resource already started on XID " + this.xid + " - cannot start it on more than one XID at a time", XAException.XAER_RMERR);
+ }
+ }
+ else if (state == PREPARED) {
+ throw new MyBatisXAException(id + ": resource already prepared on XID " + this.xid, XAException.XAER_PROTO);
+ }
- //@Override
- public boolean setTransactionTimeout(int second) throws XAException {
- transactionTimeout = second;
- return true;
- }
+ state = STARTED;
+ parentSuspend(xid);
+ }
- //@Override
- public void start(Xid xid, int flag) throws XAException {
- if(log.isDebugEnabled()) {
- log.debug(id + " start flag:" + printFlag(flag) + " " + xid);
- }
+ //@Override
+ public void end(Xid xid, int flag) throws XAException {
+ if(log.isDebugEnabled()) log.debug(id + ": call end old state=" + xlatedState() + ", XID=" + xid + " and flag " + decodeXAResourceFlag(flag));
- if(flag == XAResource.TMNOFLAGS) {
- parentSuspend(xid);
- }
- // TODO XAResource.TMRESUME ?
- }
+ if (flag != XAResource.TMSUCCESS && flag != XAResource.TMFAIL)
+ throw new MyBatisXAException(id + ": unsupported end flag " + decodeXAResourceFlag(flag), XAException.XAER_RMERR);
+ if (xid == null)
+ throw new MyBatisXAException(id + ": XID cannot be null", XAException.XAER_INVAL);
- private String printFlag(int flag) {
- switch(flag) {
- case XAResource.TMENDRSCAN: return "TMENDRSCAN";
- case XAResource.TMFAIL: return "TMFAIL";
- case XAResource.TMJOIN: return "TMJOIN";
- case XAResource.TMNOFLAGS: return "TMNOFLAGS";
- case XAResource.TMONEPHASE: return "TMONEPHASE";
- case XAResource.TMRESUME: return "TMRESUME";
- case XAResource.TMSTARTRSCAN: return "TMSTARTRSCAN";
- case XAResource.TMSUCCESS: return "TMSUCCESS";
- case XAResource.TMSUSPEND: return "TMSUSPEND";
- default: return "" + flag;
- }
- }
+ if (state == NO_TX) {
+ throw new MyBatisXAException(id + ": resource never started on XID " + xid, XAException.XAER_PROTO);
+ }
+ else if (state == STARTED) {
+ if (this.xid.equals(xid)) {
+ if (log.isDebugEnabled()) log.debug(id + ": OK to end, old state=" + xlatedState() + ", XID=" + xid + ", flag=" + decodeXAResourceFlag(flag));
+ }
+ else
+ throw new MyBatisXAException(id + ": resource already started on XID " + this.xid + " - cannot end it on another XID " + xid, XAException.XAER_PROTO);
+ }
+ else if (state == ENDED) {
+ throw new MyBatisXAException(id + ": resource already ended on XID " + xid, XAException.XAER_PROTO);
+ }
+ else if (state == PREPARED) {
+ throw new MyBatisXAException(id + ": cannot end, resource already prepared on XID " + xid, XAException.XAER_PROTO);
+ }
- private void parentSuspend(Xid xid) {
- if(log.isDebugEnabled()) {
- log.debug(id + " suspend parent session " + xid);
- }
+ if (flag == XAResource.TMFAIL) {
+ // Rollback transaction. After call method end() call medod roolback()
+ if (log.isDebugEnabled()) log.debug(id + ": after end TMFAIL reset state to ENDED and roolback");
+ }
- byte[] trId = xid.getGlobalTransactionId();
- GlobalKey key = new GlobalKey(trId);
- GlobalToken globalToken = globalTokens.get(key);
+ this.state = ENDED;
+ }
- if(globalToken == null) {
- if(log.isDebugEnabled()) {
- log.debug(id + " add GlobalToken " + key);
- }
+ //@Override
+ public int prepare(Xid xid) throws XAException {
+ if(log.isDebugEnabled()) log.debug(id + ": call prepare old state=" + xlatedState() + ", XID=" + xid);
- globalTokens.put(key, globalToken = new GlobalToken());
- } else {
- if(log.isDebugEnabled()) {
- log.debug(id + " present GlobalToken " + key);
- }
- }
- globalToken.parentSuspend(id, sqlSessionManager);
- }
+ if (xid == null)
+ throw new MyBatisXAException(id + ": XID cannot be null", XAException.XAER_INVAL);
- private void parentResume(Xid xid) {
- if(log.isDebugEnabled()) {
- log.debug(id + " resume parent session " + xid);
- }
+ if (state == NO_TX) {
+ throw new MyBatisXAException(id + ": resource never started on XID " + xid, XAException.XAER_PROTO);
+ }
+ else if (state == STARTED) {
+ throw new MyBatisXAException(id + ": resource never ended on XID " + xid, XAException.XAER_PROTO);
+ }
+ else if (state == ENDED) {
+ if (this.xid.equals(xid)) {
+ if (log.isDebugEnabled()) log.debug(id + ": OK to prepare, old state=" + xlatedState() + ", XID=" + xid);
+ }
+ else
+ throw new MyBatisXAException(id + ": resource already started on XID " + this.xid + " - cannot prepare it on another XID " + xid, XAException.XAER_PROTO);
+ }
+ else if (state == PREPARED) {
+ throw new MyBatisXAException(id + ": resource already prepared on XID " + this.xid, XAException.XAER_PROTO);
+ }
- byte[] trId = xid.getGlobalTransactionId();
- GlobalKey key = new GlobalKey(trId);
- GlobalToken globalToken = globalTokens.get(key);
+ this.state = PREPARED;
+ return XAResource.XA_OK;
+ }
- if(globalToken != null) {
- globalToken.parentResume(id, sqlSessionManager);
+ //@Override
+ public void commit(Xid xid, boolean onePhase) throws XAException {
+ if(log.isDebugEnabled()) log.debug(id + ": call commit old state=" + xlatedState() + ", XID=" + xid + " onePhase is " + onePhase);
- if(globalToken.isEmpty()) {
- if(log.isDebugEnabled()) {
- log.debug(id + " remove GlobalToken " + key);
- }
+ if (xid == null)
+ throw new MyBatisXAException(id + ": XID cannot be null", XAException.XAER_INVAL);
- globalTokens.remove(key);
- } else {
- if(log.isDebugEnabled()) {
- log.debug(id + " not remove GlobalToken " + key);
- }
- }
- } else {
- if(log.isDebugEnabled()) {
- log.debug(id + " not find GlobalToken " + key);
- }
- }
- }
+ if (state == NO_TX) {
+ throw new MyBatisXAException(id + ": resource never started on XID " + xid, XAException.XAER_PROTO);
+ }
+ else if (state == STARTED) {
+ throw new MyBatisXAException(id + ": resource never ended on XID " + xid, XAException.XAER_PROTO);
+ }
+ else if (state == ENDED) {
+ if (onePhase) {
+ if (log.isDebugEnabled()) log.debug(id + ": OK to commit with 1PC, old state=" + xlatedState() + ", XID=" + xid);
+ }
+ else
+ throw new MyBatisXAException(id + ": resource never prepared on XID " + xid, XAException.XAER_PROTO);
+ }
+ else if (state == PREPARED) {
+ if (!onePhase) {
+ if (this.xid.equals(xid)) {
+ if (log.isDebugEnabled()) log.debug(id + ": OK to commit, old state=" + xlatedState() + ", XID=" + xid);
+ }
+ else
+ throw new MyBatisXAException(id + ": resource already started on XID " + this.xid + " - cannot commit it on another XID " + xid, XAException.XAER_PROTO);
+ }
+ else
+ throw new MyBatisXAException(id + ": cannot commit in one phase as resource has been prepared on XID " + xid, XAException.XAER_PROTO);
+ }
- static class GlobalKey {
- final byte[] globalId;
- final int arrayHash;
+ try {
+ parentResume(xid);
+ } finally {
+ if (log.isDebugEnabled()) log.debug(id + ": after commit reset state to NO_TX");
+ this.state = NO_TX;
+ this.xid = null;
+ }
+ }
- public GlobalKey(byte[] globalId) {
- this.globalId = globalId;
- this.arrayHash = Arrays.hashCode(globalId);
- }
+ //@Override
+ public void rollback(Xid xid) throws XAException {
+ if(log.isDebugEnabled()) log.debug(id + ": call roolback old state=" + xlatedState() + ", XID=" + xid);
- @Override
- public int hashCode() {
- return arrayHash;
- }
+ if (xid == null)
+ throw new MyBatisXAException(id + ": XID cannot be null", XAException.XAER_INVAL);
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- GlobalKey other = (GlobalKey) obj;
- if (!Arrays.equals(globalId, other.globalId))
- return false;
- return true;
- }
+ if (state == NO_TX) {
+ throw new MyBatisXAException(id + ": resource never started on XID " + xid, XAException.XAER_PROTO);
+ }
+ else if (state == STARTED) {
+ throw new MyBatisXAException(id + ": resource never ended on XID " + xid, XAException.XAER_PROTO);
+ }
+ else if (state == ENDED) {
+ if (this.xid.equals(xid)) {
+ if (log.isDebugEnabled()) log.debug(id + ": OK to rollback, old state=" + xlatedState() + ", XID=" + xid);
+ }
+ else
+ throw new MyBatisXAException(id + ": resource already started on XID " + this.xid + " - cannot roll it back on another XID " + xid, XAException.XAER_PROTO);
+ }
+ else if (state == PREPARED) {
+ if (log.isDebugEnabled()) log.debug(id + ": rollback reset state from PREPARED to NO_TX");
+ this.state = NO_TX;
+ throw new MyBatisXAException(id + ": resource committed during prepare on XID " + this.xid, XAException.XA_HEURCOM);
+ }
- @Override
- public String toString() {
- StringBuilder s = new StringBuilder();
- s.append("[Xid:globalId=");
- for (int i = 0; i < globalId.length; i++) {
- s.append(Integer.toHexString(globalId[i]));
- }
- s.append(",length=").append(globalId.length);
- return s.toString();
- }
- }
+ try {
+ parentResume(xid);
+ } finally {
+ if (log.isDebugEnabled()) log.debug(id + ": after rollback reset state to NO_TX");
+ this.state = NO_TX;
+ this.xid = null;
+ }
+ }
- static class GlobalToken {
- private final Log log = LogFactory.getLog(getClass());
- IdentityHashMap<SqlSessionManager, Token> tokens = new IdentityHashMap<SqlSessionManager, XASqlSessionManager.Token>();
+ private void parentSuspend(Xid xid) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + ": suspend parent session " + xid);
+ }
- public GlobalToken() {
- }
+ byte[] trId = xid.getGlobalTransactionId();
+ GlobalKey key = new GlobalKey(trId);
+ GlobalToken globalToken = globalTokens.get(key);
- void parentSuspend(String id, SqlSessionManager sqlSessionManager) {
- Token token = tokens.get(sqlSessionManager);
+ if(globalToken == null) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + ": add GlobalToken " + key);
+ }
- if(token == null) {
- if(log.isDebugEnabled()) {
- log.debug(id + " add Token " + sqlSessionManager);
- }
+ globalTokens.put(key, globalToken = new GlobalToken());
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + ": present GlobalToken " + key);
+ }
+ }
+ globalToken.parentSuspend(id, sqlSessionManager);
+ }
- token = new Token(sqlSessionManager);
- tokens.put(sqlSessionManager, token);
- } else {
- if(log.isDebugEnabled()) {
- log.debug(id + " present Token " + sqlSessionManager);
- }
- }
- token.parentSuspend(id);
- }
+ private void parentResume(Xid xid) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + ": resume parent session " + xid);
+ }
- void parentResume(String id, SqlSessionManager sqlSessionManager) {
- Token token = tokens.get(sqlSessionManager);
+ byte[] trId = xid.getGlobalTransactionId();
+ GlobalKey key = new GlobalKey(trId);
+ GlobalToken globalToken = globalTokens.get(key);
- if(token != null) {
- token.parentResume(id);
+ if(globalToken != null) {
+ globalToken.parentResume(id, sqlSessionManager);
- // remove last
- if(token.isFirst()) {
- if(log.isDebugEnabled()) {
- log.debug(id + " remove parent session " + sqlSessionManager);
- }
+ if(globalToken.isEmpty()) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + ": remove GlobalToken " + key);
+ }
- tokens.remove(sqlSessionManager);
- }
- } else {
- if(log.isDebugEnabled()) {
- log.debug(id + " not find parent session " + sqlSessionManager);
- }
- }
- }
+ globalTokens.remove(key);
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + ": not remove GlobalToken " + key);
+ }
+ }
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + ": not find GlobalToken " + key);
+ }
+ }
+ }
- boolean isEmpty() {
- return tokens.isEmpty();
- }
- }
+ static class GlobalKey {
+ final byte[] globalId;
+ final int arrayHash;
- static class Token {
- private final Log log = LogFactory.getLog(getClass());
- final SqlSessionManager sqlSessionManager;
- ThreadLocal<SqlSession> localSqlSession;
- SqlSession suspendedSqlSession;
- int count;
+ public GlobalKey(byte[] globalId) {
+ this.globalId = globalId;
+ this.arrayHash = Arrays.hashCode(globalId);
+ }
- @SuppressWarnings("unchecked")
- public Token(SqlSessionManager sqlSessionManager) {
- this.sqlSessionManager = sqlSessionManager;
- this.count = 0;
- try {
- Field field = SqlSessionManager.class.getDeclaredField("localSqlSession");
- field.setAccessible(true);
- localSqlSession = (ThreadLocal<SqlSession>) field.get(sqlSessionManager);
- } catch(Exception e) {
- }
- }
+ @Override
+ public int hashCode() {
+ return arrayHash;
+ }
- boolean isFirst() {
- return count == 0;
- }
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ GlobalKey other = (GlobalKey) obj;
+ if (!Arrays.equals(globalId, other.globalId))
+ return false;
+ return true;
+ }
- void parentSuspend(String id) {
- if(isFirst()) {
- if(log.isDebugEnabled()) {
- log.debug(id + " suspend parent session");
- }
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+ s.append("[Xid:globalId=");
+ for (int i = 0; i < globalId.length; i++) {
+ s.append(Integer.toHexString(globalId[i]));
+ }
+ s.append(",length=").append(globalId.length);
+ return s.toString();
+ }
+ }
- if(localSqlSession != null) {
- suspendedSqlSession = localSqlSession.get();
- localSqlSession.remove();
- }
- } else {
- if(log.isDebugEnabled()) {
- log.debug(id + " skip suspend parent session");
- }
- }
- count++;
- }
+ static class GlobalToken {
+ private final Log log = LogFactory.getLog(getClass());
+ IdentityHashMap<SqlSessionManager, Token> tokens = new IdentityHashMap<SqlSessionManager, XASqlSessionManager.Token>();
- void parentResume(String id) {
- if(count > 0)
- count--;
+ public GlobalToken() {
+ }
- if(isFirst()) {
- if(log.isDebugEnabled()) {
- log.debug(id + " resume parent session");
- }
+ void parentSuspend(String id, SqlSessionManager sqlSessionManager) {
+ Token token = tokens.get(sqlSessionManager);
- if(localSqlSession != null) {
- if(suspendedSqlSession == null) {
- localSqlSession.remove();
- } else {
- localSqlSession.set(suspendedSqlSession);
- suspendedSqlSession = null;
- }
- }
- } else {
- if(log.isDebugEnabled()) {
- log.debug(id + " skip resume parent session");
- }
- }
- }
- }
+ if(token == null) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + ": add Token " + sqlSessionManager);
+ }
+
+ token = new Token(sqlSessionManager);
+ tokens.put(sqlSessionManager, token);
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + ": present Token " + sqlSessionManager);
+ }
+ }
+ token.parentSuspend(id);
+ }
+
+ void parentResume(String id, SqlSessionManager sqlSessionManager) {
+ Token token = tokens.get(sqlSessionManager);
+
+ if(token != null) {
+ token.parentResume(id);
+
+ // remove last
+ if(token.isFirst()) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + ": remove parent session " + sqlSessionManager);
+ }
+
+ tokens.remove(sqlSessionManager);
+ }
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + ": not find parent session " + sqlSessionManager);
+ }
+ }
+ }
+
+ boolean isEmpty() {
+ return tokens.isEmpty();
+ }
+ }
+
+ static class Token {
+ private final Log log = LogFactory.getLog(getClass());
+ final SqlSessionManager sqlSessionManager;
+ ThreadLocal<SqlSession> localSqlSession;
+ SqlSession suspendedSqlSession;
+ int count;
+
+ @SuppressWarnings("unchecked")
+ public Token(SqlSessionManager sqlSessionManager) {
+ this.sqlSessionManager = sqlSessionManager;
+ this.count = 0;
+ try {
+ Field field = SqlSessionManager.class.getDeclaredField("localSqlSession");
+ field.setAccessible(true);
+ localSqlSession = (ThreadLocal<SqlSession>) field.get(sqlSessionManager);
+ } catch(Exception e) {
+ }
+ }
+
+ boolean isFirst() {
+ return count == 0;
+ }
+
+ void parentSuspend(String id) {
+ if(isFirst()) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " suspend parent session");
+ }
+
+ if(localSqlSession != null) {
+ suspendedSqlSession = localSqlSession.get();
+ localSqlSession.remove();
+ }
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " skip suspend parent session");
+ }
+ }
+ count++;
+ }
+
+ void parentResume(String id) {
+ if(count > 0)
+ count--;
+
+ if(isFirst()) {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " resume parent session");
+ }
+
+ if(localSqlSession != null) {
+ if(suspendedSqlSession == null) {
+ localSqlSession.remove();
+ } else {
+ localSqlSession.set(suspendedSqlSession);
+ suspendedSqlSession = null;
+ }
+ }
+ } else {
+ if(log.isDebugEnabled()) {
+ log.debug(id + " skip resume parent session");
+ }
+ }
+ }
+ }
}
--
1.9.5.github.0
From 5e96997323ad365c26e541b0801b1b3374b76ae9 Mon Sep 17 00:00:00 2001
From: Christian Poitras <christian.poitras@ircm.qc.ca>
Date: Tue, 31 Mar 2015 10:35:22 -0400
Subject: [PATCH 4/6] Users can choose the implementation of
XASqlSessionManager. See #29.
---
pom.xml | 12 ++++-----
.../guice/transactional/XASqlSessionManager.java | 5 ++--
.../transactional/XASqlSessionManagerProvider.java | 30 +++++++++++++++++++++
.../geronimo/NamedXASqlSessionManager.java | 30 +++++++++++++++++++++
.../geronimo/NamedXASqlSessionManagerProvider.java | 31 ++++++++++++++++++++++
5 files changed, 99 insertions(+), 9 deletions(-)
create mode 100644 src/main/java/org/mybatis/guice/transactional/XASqlSessionManagerProvider.java
create mode 100644 src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManager.java
create mode 100644 src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManagerProvider.java
diff --git a/pom.xml b/pom.xml
index 86ca24f..a6e65f2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -176,6 +176,12 @@
<version>6.0-5</version>
<optional>true</optional>
</dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.components</groupId>
+ <artifactId>geronimo-connector</artifactId>
+ <version>3.1.1</version>
+ <optional>true</optional>
+ </dependency>
<!-- JTA test -->
<dependency>
@@ -191,12 +197,6 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>org.apache.geronimo.components</groupId>
- <artifactId>geronimo-connector</artifactId>
- <version>3.1.1</version>
- <!-- <scope>test</scope> -->
- </dependency>
- <dependency>
<groupId>org.apache.aries.transaction</groupId>
<artifactId>org.apache.aries.transaction.jdbc</artifactId>
<version>2.1.0</version>
diff --git a/src/main/java/org/mybatis/guice/transactional/XASqlSessionManager.java b/src/main/java/org/mybatis/guice/transactional/XASqlSessionManager.java
index 5b9642a..fc307f9 100644
--- a/src/main/java/org/mybatis/guice/transactional/XASqlSessionManager.java
+++ b/src/main/java/org/mybatis/guice/transactional/XASqlSessionManager.java
@@ -24,13 +24,12 @@ import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
-import org.apache.geronimo.transaction.manager.NamedXAResource;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionManager;
-public class XASqlSessionManager implements XAResource, NamedXAResource {
+public class XASqlSessionManager implements XAResource {
private static final Log log = LogFactory.getLog(XASqlSessionManager.class);
public static final int NO_TX = 0;
@@ -52,7 +51,7 @@ public class XASqlSessionManager implements XAResource, NamedXAResource {
}
//@Override
- public String getName() {
+ public String getId() {
return id;
}
diff --git a/src/main/java/org/mybatis/guice/transactional/XASqlSessionManagerProvider.java b/src/main/java/org/mybatis/guice/transactional/XASqlSessionManagerProvider.java
new file mode 100644
index 0000000..9b03c7c
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/transactional/XASqlSessionManagerProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2015 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 javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.apache.ibatis.session.SqlSessionManager;
+
+public class XASqlSessionManagerProvider implements Provider<XASqlSessionManager> {
+ @Inject
+ private SqlSessionManager sqlSessionManager;
+
+ public XASqlSessionManager get() {
+ return new XASqlSessionManager(sqlSessionManager);
+ }
+}
diff --git a/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManager.java b/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManager.java
new file mode 100644
index 0000000..4d8706f
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManager.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2015 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.geronimo;
+
+import org.apache.geronimo.transaction.manager.NamedXAResource;
+import org.apache.ibatis.session.SqlSessionManager;
+import org.mybatis.guice.transactional.XASqlSessionManager;
+
+public class NamedXASqlSessionManager extends XASqlSessionManager implements NamedXAResource {
+ public NamedXASqlSessionManager(SqlSessionManager sqlSessionManager) {
+ super(sqlSessionManager);
+ }
+
+ public String getName() {
+ return this.getId();
+ }
+}
diff --git a/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManagerProvider.java b/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManagerProvider.java
new file mode 100644
index 0000000..771a557
--- /dev/null
+++ b/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManagerProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010-2015 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.geronimo;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.apache.ibatis.session.SqlSessionManager;
+import org.mybatis.guice.transactional.XASqlSessionManager;
+
+public class NamedXASqlSessionManagerProvider implements Provider<XASqlSessionManager> {
+ @Inject
+ private SqlSessionManager sqlSessionManager;
+
+ public XASqlSessionManager get() {
+ return new NamedXASqlSessionManager(sqlSessionManager);
+ }
+}
--
1.9.5.github.0
From 9bc024beb18e0e11c397d154670301854aeb4c49 Mon Sep 17 00:00:00 2001
From: Christian Poitras <christian.poitras@ircm.qc.ca>
Date: Wed, 1 Apr 2015 09:21:15 -0400
Subject: [PATCH 5/6] XASqlSessionManagerProvider should provide an
implementation for XAResource rather than XASqlSessionManager since only
XAResource interface is needed by the interceptor.
---
.../java/org/mybatis/guice/MyBatisJtaModule.java | 182 +++++++------
.../TxTransactionalMethodInterceptor.java | 291 +++++++++++----------
.../transactional/XASqlSessionManagerProvider.java | 5 +-
.../geronimo/NamedXASqlSessionManager.java | 30 ---
.../geronimo/NamedXASqlSessionManagerProvider.java | 17 +-
5 files changed, 254 insertions(+), 271 deletions(-)
delete mode 100644 src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManager.java
diff --git a/src/main/java/org/mybatis/guice/MyBatisJtaModule.java b/src/main/java/org/mybatis/guice/MyBatisJtaModule.java
index f22db5f..c139168 100644
--- a/src/main/java/org/mybatis/guice/MyBatisJtaModule.java
+++ b/src/main/java/org/mybatis/guice/MyBatisJtaModule.java
@@ -1,86 +1,96 @@
-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.logging.Log;
-import org.apache.ibatis.logging.LogFactory;
-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 final Log log = LogFactory.getLog(getClass());
-
- private TransactionManager transactionManager;
-
- public MyBatisJtaModule() {
- }
-
- public MyBatisJtaModule(TransactionManager transactionManager) {
- this.transactionManager = transactionManager;
- }
-
- @Override
- protected void bindTransactionInterceptors() {
- TransactionManager manager = getTransactionManager();
-
- if (manager == null) {
- log.debug("bind default transaction interceptors");
- super.bindTransactionInterceptors();
- } else {
- log.debug("bind XA transaction interceptors");
-
- // transactional interceptor
- TransactionalMethodInterceptor interceptor = new TransactionalMethodInterceptor();
- requestInjection(interceptor);
-
- // jta transactional interceptor
- TxTransactionalMethodInterceptor interceptorTx = new TxTransactionalMethodInterceptor();
- requestInjection(interceptorTx);
-
- bind(TransactionManager.class).toInstance(manager);
-
- bindInterceptor(any(), not(DECLARED_BY_OBJECT).and(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(DECLARED_BY_OBJECT).and(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;
- }
-
- }
-}
+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 static org.mybatis.guice.Preconditions.checkArgument;
+
+import javax.inject.Provider;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+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;
+import org.mybatis.guice.transactional.XASqlSessionManagerProvider;
+
+public abstract class MyBatisJtaModule extends MyBatisModule {
+ private final Log log = LogFactory.getLog(getClass());
+
+ private TransactionManager transactionManager;
+ private Class<? extends Provider<? extends XAResource>> xaSqlSessionManagerProvider = XASqlSessionManagerProvider.class;
+
+ public MyBatisJtaModule() {
+ }
+
+ public MyBatisJtaModule(TransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+
+ @Override
+ protected void bindTransactionInterceptors() {
+ TransactionManager manager = getTransactionManager();
+
+ if (manager == null) {
+ log.debug("bind default transaction interceptors");
+ super.bindTransactionInterceptors();
+ } else {
+ log.debug("bind XA transaction interceptors");
+
+ // transactional interceptor
+ TransactionalMethodInterceptor interceptor = new TransactionalMethodInterceptor();
+ requestInjection(interceptor);
+
+ // jta transactional interceptor
+ TxTransactionalMethodInterceptor interceptorTx = new TxTransactionalMethodInterceptor();
+ requestInjection(interceptorTx);
+ bind(XAResource.class).toProvider(xaSqlSessionManagerProvider);
+
+ bind(TransactionManager.class).toInstance(manager);
+
+ bindInterceptor(any(), not(DECLARED_BY_OBJECT).and(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(DECLARED_BY_OBJECT).and(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 void bindXASqlSessionManagerProviderType(Class<? extends Provider<? extends XAResource>> xaSqlSessionManagerProvider) {
+ checkArgument(xaSqlSessionManagerProvider != null, "Parameter 'xaSqlSessionManagerProvider' must be not null");
+ this.xaSqlSessionManagerProvider = xaSqlSessionManagerProvider;
+ }
+
+ 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/TxTransactionalMethodInterceptor.java b/src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java
index 6ec91a3..f629e79 100644
--- a/src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java
+++ b/src/main/java/org/mybatis/guice/transactional/TxTransactionalMethodInterceptor.java
@@ -1,144 +1,147 @@
-/*
- * Copyright 2010-2015 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.apache.ibatis.session.SqlSessionManager;
-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;
- @Inject private SqlSessionManager sqlSessionManager;
-
- 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());
- }
-
- boolean needsRollback = transactional.rollbackOnly();
- 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);
-
- log.debug("enlistResource XASqlSessionManager");
- XASqlSessionManager xaRes = new XASqlSessionManager(sqlSessionManager);
- tranToken.getActiveTransaction().enlistResource(xaRes);
-
- try {
- if(log.isDebugEnabled()) {
- log.debug(format("%s - Tx Transaction %s (CompletionAllowed %s) call method",
- debugPrefix,
- attribute.name(),
- tranToken.isCompletionAllowed()));
- }
- object = invocation.proceed();
-
- if(needsRollback)
- manager.setRollbackOnly();
-
- } 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;
- }
-
-}
+/*
+ * Copyright 2010-2015 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.inject.Provider;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+
+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;
+ @Inject
+ private Provider<XAResource> xaResourceProvider;
+
+ 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());
+ }
+
+ boolean needsRollback = transactional.rollbackOnly();
+ 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);
+
+ log.debug("enlistResource XASqlSessionManager");
+ XAResource xaRes = xaResourceProvider.get();
+ tranToken.getActiveTransaction().enlistResource(xaRes);
+
+ try {
+ if(log.isDebugEnabled()) {
+ log.debug(format("%s - Tx Transaction %s (CompletionAllowed %s) call method",
+ debugPrefix,
+ attribute.name(),
+ tranToken.isCompletionAllowed()));
+ }
+ object = invocation.proceed();
+
+ if(needsRollback)
+ manager.setRollbackOnly();
+
+ } 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;
+ }
+
+}
diff --git a/src/main/java/org/mybatis/guice/transactional/XASqlSessionManagerProvider.java b/src/main/java/org/mybatis/guice/transactional/XASqlSessionManagerProvider.java
index 9b03c7c..1ed37f8 100644
--- a/src/main/java/org/mybatis/guice/transactional/XASqlSessionManagerProvider.java
+++ b/src/main/java/org/mybatis/guice/transactional/XASqlSessionManagerProvider.java
@@ -17,14 +17,15 @@ package org.mybatis.guice.transactional;
import javax.inject.Inject;
import javax.inject.Provider;
+import javax.transaction.xa.XAResource;
import org.apache.ibatis.session.SqlSessionManager;
-public class XASqlSessionManagerProvider implements Provider<XASqlSessionManager> {
+public class XASqlSessionManagerProvider implements Provider<XAResource> {
@Inject
private SqlSessionManager sqlSessionManager;
- public XASqlSessionManager get() {
+ public XAResource get() {
return new XASqlSessionManager(sqlSessionManager);
}
}
diff --git a/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManager.java b/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManager.java
deleted file mode 100644
index 4d8706f..0000000
--- a/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManager.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2010-2015 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.geronimo;
-
-import org.apache.geronimo.transaction.manager.NamedXAResource;
-import org.apache.ibatis.session.SqlSessionManager;
-import org.mybatis.guice.transactional.XASqlSessionManager;
-
-public class NamedXASqlSessionManager extends XASqlSessionManager implements NamedXAResource {
- public NamedXASqlSessionManager(SqlSessionManager sqlSessionManager) {
- super(sqlSessionManager);
- }
-
- public String getName() {
- return this.getId();
- }
-}
diff --git a/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManagerProvider.java b/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManagerProvider.java
index 771a557..44b9a64 100644
--- a/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManagerProvider.java
+++ b/src/main/java/org/mybatis/guice/transactional/geronimo/NamedXASqlSessionManagerProvider.java
@@ -15,17 +15,16 @@
*/
package org.mybatis.guice.transactional.geronimo;
-import javax.inject.Inject;
-import javax.inject.Provider;
+import javax.transaction.xa.XAResource;
-import org.apache.ibatis.session.SqlSessionManager;
+import org.apache.geronimo.transaction.manager.WrapperNamedXAResource;
import org.mybatis.guice.transactional.XASqlSessionManager;
+import org.mybatis.guice.transactional.XASqlSessionManagerProvider;
-public class NamedXASqlSessionManagerProvider implements Provider<XASqlSessionManager> {
- @Inject
- private SqlSessionManager sqlSessionManager;
-
- public XASqlSessionManager get() {
- return new NamedXASqlSessionManager(sqlSessionManager);
+public class NamedXASqlSessionManagerProvider extends XASqlSessionManagerProvider {
+ public XAResource get() {
+ XAResource xaResource = super.get();
+ String name = ((XASqlSessionManager) xaResource).getId();
+ return new WrapperNamedXAResource(xaResource, name);
}
}
--
1.9.5.github.0
From 4f56e5ac999ede49aeff1512a7399d1084a08fba Mon Sep 17 00:00:00 2001
From: Christian Poitras <christian.poitras@ircm.qc.ca>
Date: Thu, 2 Apr 2015 09:51:22 -0400
Subject: [PATCH 6/6] Solved injection of custom XAResource into interceptor.
---
.../org/mybatis/guice/AbstractMyBatisModule.java | 288 ++++++++++-----------
.../java/org/mybatis/guice/MyBatisJtaModule.java | 10 +-
.../guice/jta/CustomXaResourceProvider.java | 19 ++
.../mybatis/guice/jta/JtaCustomXaResourceTest.java | 118 +++++++++
4 files changed, 286 insertions(+), 149 deletions(-)
create mode 100644 src/test/java/org/mybatis/guice/jta/CustomXaResourceProvider.java
create mode 100644 src/test/java/org/mybatis/guice/jta/JtaCustomXaResourceTest.java
diff --git a/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java b/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
index cb894e7..b516f45 100644
--- a/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
+++ b/src/main/java/org/mybatis/guice/AbstractMyBatisModule.java
@@ -1,144 +1,144 @@
-/**
- * Copyright 2009-2015 the original author or authors.
- *
- * 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;
-
-import com.google.inject.AbstractModule;
-import com.google.inject.Binder;
-import com.google.inject.Scopes;
-import com.google.inject.matcher.AbstractMatcher;
-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 java.lang.reflect.Method;
-
-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;
-
-/**
- *
- * @version $Id$
- */
-abstract class AbstractMyBatisModule extends AbstractModule {
-
- protected static final AbstractMatcher<Method> DECLARED_BY_OBJECT = new AbstractMatcher<Method>() {
- public boolean matches(Method method) {
- return method.getDeclaringClass() == Object.class;
- }
- };
-
- private ClassLoader resourcesClassLoader = getDefaultClassLoader();
-
- private ClassLoader driverClassLoader = getDefaultClassLoader();
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected final void configure() {
- try {
- // sql session manager
- bind(SqlSessionManager.class).toProvider(SqlSessionManagerProvider.class).in(Scopes.SINGLETON);
- bind(SqlSession.class).to(SqlSessionManager.class).in(Scopes.SINGLETON);
-
- bindTransactionInterceptors();
-
- internalConfigure();
-
- bind(ClassLoader.class)
- .annotatedWith(named("JDBC.driverClassLoader"))
- .toInstance(driverClassLoader);
- } finally {
- resourcesClassLoader = getDefaultClassLoader();
- driverClassLoader = getDefaultClassLoader();
- }
- }
-
- /**
- * bind transactional interceptors
- */
- protected void bindTransactionInterceptors() {
- // 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);
- }
-
- /**
- *
- * @param mapperType
- */
- final <T> void bindMapper(Class<T> mapperType) {
- bind(mapperType).toProvider(guicify(new MapperProvider<T>(mapperType))).in(Scopes.SINGLETON);
- }
-
- /**
- *
- * @return
- * @since 3.3
- */
- public void useResourceClassLoader(ClassLoader resourceClassLoader) {
- this.resourcesClassLoader = resourceClassLoader;
- }
-
- /**
- *
- * @return
- * @since 3.3
- */
- protected final ClassLoader getResourceClassLoader() {
- return resourcesClassLoader;
- }
-
- /**
- *
- * @return
- * @since 3.3
- */
- public void useJdbcDriverClassLoader(ClassLoader driverClassLoader) {
- this.driverClassLoader = driverClassLoader;
- }
-
- /**
- *
- * @return
- * @since 3.3
- */
- private ClassLoader getDefaultClassLoader() {
- return getClass().getClassLoader();
- }
-
- /**
- * Configures a {@link Binder} via the exposed methods.
- */
- abstract void internalConfigure();
-
- /**
- *
- */
- protected abstract void initialize();
-
-}
+/**
+ * Copyright 2009-2015 the original author or authors.
+ *
+ * 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;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.Scopes;
+import com.google.inject.matcher.AbstractMatcher;
+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 java.lang.reflect.Method;
+
+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;
+
+/**
+ *
+ * @version $Id$
+ */
+abstract class AbstractMyBatisModule extends AbstractModule {
+
+ protected static final AbstractMatcher<Method> DECLARED_BY_OBJECT = new AbstractMatcher<Method>() {
+ public boolean matches(Method method) {
+ return method.getDeclaringClass() == Object.class;
+ }
+ };
+
+ private ClassLoader resourcesClassLoader = getDefaultClassLoader();
+
+ private ClassLoader driverClassLoader = getDefaultClassLoader();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected final void configure() {
+ try {
+ // sql session manager
+ bind(SqlSessionManager.class).toProvider(SqlSessionManagerProvider.class).in(Scopes.SINGLETON);
+ bind(SqlSession.class).to(SqlSessionManager.class).in(Scopes.SINGLETON);
+
+ internalConfigure();
+
+ bindTransactionInterceptors();
+
+ bind(ClassLoader.class)
+ .annotatedWith(named("JDBC.driverClassLoader"))
+ .toInstance(driverClassLoader);
+ } finally {
+ resourcesClassLoader = getDefaultClassLoader();
+ driverClassLoader = getDefaultClassLoader();
+ }
+ }
+
+ /**
+ * bind transactional interceptors
+ */
+ protected void bindTransactionInterceptors() {
+ // 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);
+ }
+
+ /**
+ *
+ * @param mapperType
+ */
+ final <T> void bindMapper(Class<T> mapperType) {
+ bind(mapperType).toProvider(guicify(new MapperProvider<T>(mapperType))).in(Scopes.SINGLETON);
+ }
+
+ /**
+ *
+ * @return
+ * @since 3.3
+ */
+ public void useResourceClassLoader(ClassLoader resourceClassLoader) {
+ this.resourcesClassLoader = resourceClassLoader;
+ }
+
+ /**
+ *
+ * @return
+ * @since 3.3
+ */
+ protected final ClassLoader getResourceClassLoader() {
+ return resourcesClassLoader;
+ }
+
+ /**
+ *
+ * @return
+ * @since 3.3
+ */
+ public void useJdbcDriverClassLoader(ClassLoader driverClassLoader) {
+ this.driverClassLoader = driverClassLoader;
+ }
+
+ /**
+ *
+ * @return
+ * @since 3.3
+ */
+ private ClassLoader getDefaultClassLoader() {
+ return getClass().getClassLoader();
+ }
+
+ /**
+ * Configures a {@link Binder} via the exposed methods.
+ */
+ abstract void internalConfigure();
+
+ /**
+ *
+ */
+ protected abstract void initialize();
+
+}
diff --git a/src/main/java/org/mybatis/guice/MyBatisJtaModule.java b/src/main/java/org/mybatis/guice/MyBatisJtaModule.java
index c139168..1174f1d 100644
--- a/src/main/java/org/mybatis/guice/MyBatisJtaModule.java
+++ b/src/main/java/org/mybatis/guice/MyBatisJtaModule.java
@@ -23,7 +23,7 @@ public abstract class MyBatisJtaModule extends MyBatisModule {
private final Log log = LogFactory.getLog(getClass());
private TransactionManager transactionManager;
- private Class<? extends Provider<? extends XAResource>> xaSqlSessionManagerProvider = XASqlSessionManagerProvider.class;
+ private Class<? extends Provider<? extends XAResource>> xaResourceProvider = XASqlSessionManagerProvider.class;
public MyBatisJtaModule() {
}
@@ -49,7 +49,7 @@ public abstract class MyBatisJtaModule extends MyBatisModule {
// jta transactional interceptor
TxTransactionalMethodInterceptor interceptorTx = new TxTransactionalMethodInterceptor();
requestInjection(interceptorTx);
- bind(XAResource.class).toProvider(xaSqlSessionManagerProvider);
+ bind(XAResource.class).toProvider(xaResourceProvider);
bind(TransactionManager.class).toInstance(manager);
@@ -76,9 +76,9 @@ public abstract class MyBatisJtaModule extends MyBatisModule {
bindTransactionFactoryType(factoryType);
}
- protected void bindXASqlSessionManagerProviderType(Class<? extends Provider<? extends XAResource>> xaSqlSessionManagerProvider) {
- checkArgument(xaSqlSessionManagerProvider != null, "Parameter 'xaSqlSessionManagerProvider' must be not null");
- this.xaSqlSessionManagerProvider = xaSqlSessionManagerProvider;
+ protected void bindXAResourceProvider(Class<? extends Provider<? extends XAResource>> xaResourceProvider) {
+ checkArgument(xaResourceProvider != null, "Parameter 'xaResourceProvider' must be not null");
+ this.xaResourceProvider = xaResourceProvider;
}
protected static class ProviderImpl<T> implements Provider<T> {
diff --git a/src/test/java/org/mybatis/guice/jta/CustomXaResourceProvider.java b/src/test/java/org/mybatis/guice/jta/CustomXaResourceProvider.java
new file mode 100644
index 0000000..2a051e1
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/CustomXaResourceProvider.java
@@ -0,0 +1,19 @@
+package org.mybatis.guice.jta;
+
+import javax.transaction.xa.XAResource;
+
+import org.mybatis.guice.transactional.XASqlSessionManagerProvider;
+
+public class CustomXaResourceProvider extends XASqlSessionManagerProvider {
+ private static boolean providerCalled = false;
+
+ @Override
+ public XAResource get() {
+ providerCalled = true;
+ return super.get();
+ }
+
+ public static boolean isProviderCalled() {
+ return providerCalled;
+ }
+}
diff --git a/src/test/java/org/mybatis/guice/jta/JtaCustomXaResourceTest.java b/src/test/java/org/mybatis/guice/jta/JtaCustomXaResourceTest.java
new file mode 100644
index 0000000..a37bfe6
--- /dev/null
+++ b/src/test/java/org/mybatis/guice/jta/JtaCustomXaResourceTest.java
@@ -0,0 +1,118 @@
+package org.mybatis.guice.jta;
+
+import static org.junit.Assert.assertTrue;
+
+import javax.sql.DataSource;
+
+import org.apache.aries.transaction.AriesTransactionManager;
+import org.apache.aries.transaction.internal.AriesTransactionManagerImpl;
+import org.apache.ibatis.logging.LogFactory;
+import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.mybatis.guice.MyBatisJtaModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.PrivateModule;
+
+public class JtaCustomXaResourceTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(JtaCustomXaResourceTest.class);
+
+ static AriesTransactionManager manager;
+ static DataSource dataSource2;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
+ LogFactory.useSlf4jLogging();
+
+ manager = new AriesTransactionManagerImpl();
+
+ dataSource2 = BaseDB.createLocalDataSource(BaseDB.NAME_DB2, BaseDB.URL_DB2, manager);
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ BaseDB.dropTable(BaseDB.URL_DB2);
+ }
+
+ @Rule
+ public TestName testName = new TestName();
+ private Injector injector;
+
+ JtaProcess process;
+
+ @Before
+ public void setup() throws Exception {
+ LOGGER.info("********************************************************************************");
+ LOGGER.info("Testing: " + testName.getMethodName() + "(" + getClass().getName() + ")");
+ LOGGER.info("********************************************************************************");
+ LogFactory.useSlf4jLogging();
+
+
+ LOGGER.info("create injector");
+ injector = Guice.createInjector(
+ new PrivateModule() {
+
+ @Override
+ protected void configure() {
+ install(new MyBatisJtaModule(manager) {
+
+ @Override
+ protected void initialize() {
+ environmentId("db2");
+ bindDataSourceProvider(new ProviderImpl<DataSource>(dataSource2));
+ bindDefaultTransactionProvider();
+ bindDatabaseIdProvider(new VendorDatabaseIdProvider());
+ bindXAResourceProvider(CustomXaResourceProvider.class);
+
+ addMapperClass(JtaMapper.class);
+
+ bind(JtaService2Impl.class);
+ bind(JtaProcess.class);
+ }
+ });
+
+ expose(JtaService2Impl.class);
+ expose(JtaProcess.class);
+ };
+ }
+ );
+
+ injector.injectMembers(this);
+ process = injector.getInstance(JtaProcess.class);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ BaseDB.clearTable(BaseDB.URL_DB2);
+
+ LOGGER.info("********************************************************************************");
+ LOGGER.info("Testing done: " + testName.getMethodName() + "(" + getClass().getName() + ")");
+ LOGGER.info("********************************************************************************");
+ }
+
+ /**
+ * begin REQUIRED
+ * insert(id=1)
+ * commit REQUIRED
+ *
+ * have 1 rows
+ */
+ @Test
+ public void testRequired() throws Exception {
+ try {
+ process.required(1);
+ } catch (Throwable t) {
+ }
+ assertTrue(CustomXaResourceProvider.isProviderCalled());
+ }
+}
--
1.9.5.github.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment