Last active
August 29, 2015 14:18
-
-
Save christianpoitras/34b38a0328b9a6114d4d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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