Ignite-based Implementation of Spring tx manager
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/457ca6fb Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/457ca6fb Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/457ca6fb Branch: refs/heads/ignite-1924 Commit: 457ca6fb5d64faf88869f4db7525673d067002df Parents: 2de3b19 Author: Amir Akhmedov <amir.akhme...@gmail.com> Authored: Fri Nov 20 19:03:14 2015 -0800 Committer: Valentin Kulichenko <valentin.kuliche...@gmail.com> Committed: Fri Nov 20 19:03:14 2015 -0800 ---------------------------------------------------------------------- .../spring/IgniteTransactionHolder.java | 97 ++++ .../spring/SpringTransactionManager.java | 522 +++++++++++++++++++ .../transactions/spring/package-info.java | 22 + .../test/java/config/spring-transactions.xml | 36 ++ .../testsuites/IgniteSpringTestSuite.java | 5 +- .../GridSpringTransactionManagerSelfTest.java | 165 ++++++ .../spring/GridSpringTransactionService.java | 68 +++ parent/pom.xml | 4 +- 8 files changed, 916 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/457ca6fb/modules/spring/src/main/java/org/apache/ignite/transactions/spring/IgniteTransactionHolder.java ---------------------------------------------------------------------- diff --git a/modules/spring/src/main/java/org/apache/ignite/transactions/spring/IgniteTransactionHolder.java b/modules/spring/src/main/java/org/apache/ignite/transactions/spring/IgniteTransactionHolder.java new file mode 100644 index 0000000..e2c7133 --- /dev/null +++ b/modules/spring/src/main/java/org/apache/ignite/transactions/spring/IgniteTransactionHolder.java @@ -0,0 +1,97 @@ +/* + * 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.apache.ignite.transactions.spring; + +import org.apache.ignite.transactions.Transaction; +import org.springframework.transaction.support.ResourceHolderSupport; + +/** + * A {@link org.springframework.transaction.support.ResourceHolder} for the Ignite {@link Transaction} to + * associate the transaction with a Spring transaction manager. + */ +class IgniteTransactionHolder extends ResourceHolderSupport { + /** */ + private Transaction transaction; + + /** */ + private boolean transactionActive; + + /** + * Constructs the transaction holder. + * + * @param transaction the transaction to hold + */ + IgniteTransactionHolder(Transaction transaction) { + this.transaction = transaction; + } + + /** + * Returns true if the holder is holding a transaction. + * + * @return true if holding a transaction + */ + public boolean hasTransaction() { + return this.transaction != null; + } + + /** + * Sets the transaction to be held in the resource holder. + * + * @param transaction the transaction + */ + void setTransaction(Transaction transaction) { + this.transaction = transaction; + } + + /** + * Returns the transaction in the holder or null if none has been set. + * + * @return the transaction or null + */ + Transaction getTransaction() { + return this.transaction; + } + + /** + * Return whether this holder represents an active, Ignite-managed + * transaction. + * + * @return true if a transaction is active + */ + protected boolean isTransactionActive() { + return this.transactionActive; + } + + /** + * Set whether this holder represents an active, Ignite-managed + * transaction. + * + * @param transactionActive true if a transaction is active + */ + protected void setTransactionActive(boolean transactionActive) { + this.transactionActive = transactionActive; + } + + /** {@inheritDoc} */ + @Override public void clear() { + super.clear(); + + transactionActive = false; + transaction.close(); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/457ca6fb/modules/spring/src/main/java/org/apache/ignite/transactions/spring/SpringTransactionManager.java ---------------------------------------------------------------------- diff --git a/modules/spring/src/main/java/org/apache/ignite/transactions/spring/SpringTransactionManager.java b/modules/spring/src/main/java/org/apache/ignite/transactions/spring/SpringTransactionManager.java new file mode 100644 index 0000000..d8bbbbd --- /dev/null +++ b/modules/spring/src/main/java/org/apache/ignite/transactions/spring/SpringTransactionManager.java @@ -0,0 +1,522 @@ +/* + * 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.apache.ignite.transactions.spring; + +import java.util.concurrent.TimeUnit; +import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteException; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.Ignition; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.transactions.Transaction; +import org.apache.ignite.transactions.TransactionConcurrency; +import org.apache.ignite.transactions.TransactionIsolation; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.transaction.CannotCreateTransactionException; +import org.springframework.transaction.InvalidIsolationLevelException; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionSystemException; +import org.springframework.transaction.support.AbstractPlatformTransactionManager; +import org.springframework.transaction.support.DefaultTransactionStatus; +import org.springframework.transaction.support.ResourceTransactionManager; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Implementation of Spring transaction abstraction based on Ignite transaction. + * <h1 class="header">Overview</h1> + * Spring transaction abstraction allows to enable declarative transaction management + * and concentrate on business logic rather than transaction life-cycle. + * For more information, refer to + * <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html"> + * Spring Transaction Abstraction documentation</a>. + * <h1 class="header">How To Enable Transaction support</h1> + * To enable declarative transaction management on Ignite cache in your Spring application, + * you will need to do the following: + * <ul> + * <li> + * Start an Ignite node with proper configuration in embedded mode + * (i.e., in the same JVM where the application is running). It can + * already have predefined caches, but it's not required - caches + * will be created automatically on first access if needed. + * </li> + * <li> + * Configure {@code SpringTransactionManager} as a transaction manager + * in the Spring application context. + * </li> + * </ul> + * {@code SpringTransactionManager} can start a node itself on its startup + * based on provided Ignite configuration. You can provide path to a + * Spring configuration XML file, like below (path can be absolute or + * relative to {@code IGNITE_HOME}): + * <pre name="code" class="xml"> + * <beans xmlns="http://www.springframework.org/schema/beans" + * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + * xmlns:tx="http://www.springframework.org/schema/tx" + * xsi:schemaLocation=" + * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + * http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> + * <-- Provide configuration file path. --> + * <bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"> + * <property name="configurationPath" value="examples/config/spring-transaction.xml"/> + * </bean> + * + * <-- Use annotation-driven transaction configuration. --> + * <tx:annotation-driven/> + * </beans> + * </pre> + * Or you can provide a {@link IgniteConfiguration} bean, like below: + * <pre name="code" class="xml"> + * <beans xmlns="http://www.springframework.org/schema/beans" + * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + * xmlns:tx="http://www.springframework.org/schema/tx" + * xsi:schemaLocation=" + * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + * http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> + * <-- Provide configuration bean. --> + * <bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"> + * <property name="configuration"> + * <bean id="gridCfg" class="org.apache.ignite.configuration.IgniteConfiguration"> + * ... + * </bean> + * </property> + * </bean> + * + * <-- Use annotation-driven transaction configuration. --> + * <tx:annotation-driven/> + * </beans> + * </pre> + * Note that providing both configuration path and configuration bean is illegal + * and results in {@link IllegalArgumentException}. + * + * If you already have Ignite node running within your application, + * simply provide correct Grid name, like below (if there is no Grid + * instance with such name, exception will be thrown): + * <pre name="code" class="xml"> + * <beans xmlns="http://www.springframework.org/schema/beans" + * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + * xmlns:tx="http://www.springframework.org/schema/tx" + * xsi:schemaLocation=" + * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + * http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> + * <-- Provide Grid name. --> + * <bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"> + * <property name="gridName" value="myGrid"/> + * </bean> + * + * <-- Use annotation-driven transaction configuration. --> + * <tx:annotation-driven/> + * </beans> + * </pre> + * This can be used, for example, when you are running your application + * in a J2EE Web container and use {@ignitelink org.apache.ignite.startup.servlet.ServletContextListenerStartup} + * for node startup. + * + * If neither {@link #setConfigurationPath(String) configurationPath}, + * {@link #setConfiguration(IgniteConfiguration) configuration}, nor + * {@link #setGridName(String) gridName} are provided, transaction manager + * will try to use default Grid instance (the one with the {@code null} + * name). If it doesn't exist, exception will be thrown. + * + * {@code SpringTransactionManager} can be configured to support Ignite transaction concurrency. + * For this you need to provide {@code SpringTransactionManager} with transactionConcurrency property. + * If this property is not set then default transaction concurrency will be used + * <pre name="code" class="xml"> + * <beans xmlns="http://www.springframework.org/schema/beans" + * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + * xmlns:tx="http://www.springframework.org/schema/tx" + * xsi:schemaLocation=" + * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + * http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> + * <-- Provide Grid name. --> + * <bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"> + * <property name="gridName" value="myGrid"/> + * <property name="transactionConcurrency" value="OPTIMISTIC"/> + * </bean> + * + * <-- Use annotation-driven transaction configuration. --> + * <tx:annotation-driven/> + * </beans> + * </pre> + * + * In case you need to support both "OPTIMISTIC" and "PESSIMISTIC" transaction concurrency in you application, + * you need to create two transaction managers with different transaction concurrency + * <pre name="code" class="xml"> + * <beans xmlns="http://www.springframework.org/schema/beans" + * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + * xmlns:tx="http://www.springframework.org/schema/tx" + * xsi:schemaLocation=" + * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + * http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> + * <bean id="optimisticTransactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"> + * <property name="gridName" value="myGrid"/> + * <property name="transactionConcurrency" value="OPTIMISTIC"/> + * </bean> + * + * <bean id="pessimisticTransactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"> + * <property name="gridName" value="myGrid"/> + * <property name="transactionConcurrency" value="PESSIMISTIC"/> + * </bean> + * + * <-- Use annotation-driven transaction configuration. --> + * <tx:annotation-driven/> + * </beans> + * </pre> + * Then use them with qualifiers in your application: + * <pre name="code" class="xml"> + * public class TransactionalService { + * {@literal @}Transactional("optimisticTransactionManager") + * public void doOptimistically() { + * ... + * } + * + * {@literal @}Transactional("pessimisticTransactionManager") + * public void doPessimistically() { + * ... + * } + * } + * </pre> + */ +public class SpringTransactionManager extends AbstractPlatformTransactionManager + implements ResourceTransactionManager, PlatformTransactionManager, InitializingBean { + /** + * Logger. + */ + private IgniteLogger log; + + /** + * Transaction concurrency level. + */ + private TransactionConcurrency transactionConcurrency; + + /** + * Grid configuration file path. + */ + private String cfgPath; + + /** + * Ignite configuration. + */ + private IgniteConfiguration cfg; + + /** + * Grid name. + */ + private String gridName; + + /** + * Ignite instance. + */ + private Ignite ignite; + + /** + * Constructs the transaction manager with no target Ignite instance. An + * instance must be set before use. + */ + public SpringTransactionManager() { + setNestedTransactionAllowed(false); + } + + /** + * Gets transaction concurrency level. + * + * @return Transaction concurrency level. + */ + public TransactionConcurrency getTransactionConcurrency() { + return transactionConcurrency; + } + + /** + * Sets transaction concurrency level. + * + * @param transactionConcurrency transaction concurrency level. + */ + public void setTransactionConcurrency(TransactionConcurrency transactionConcurrency) { + this.transactionConcurrency = transactionConcurrency; + } + + /** + * Gets configuration file path. + * + * @return Grid configuration file path. + */ + public String getConfigurationPath() { + return cfgPath; + } + + /** + * Sets configuration file path. + * + * @param cfgPath Grid configuration file path. + */ + public void setConfigurationPath(String cfgPath) { + this.cfgPath = cfgPath; + } + + /** + * Gets configuration bean. + * + * @return Grid configuration bean. + */ + public IgniteConfiguration getConfiguration() { + return cfg; + } + + /** + * Sets configuration bean. + * + * @param cfg Grid configuration bean. + */ + public void setConfiguration(IgniteConfiguration cfg) { + this.cfg = cfg; + } + + /** + * Gets grid name. + * + * @return Grid name. + */ + public String getGridName() { + return gridName; + } + + /** + * Sets grid name. + * + * @param gridName Grid name. + */ + public void setGridName(String gridName) { + this.gridName = gridName; + } + + /** + * {@inheritDoc} + */ + @Override public void afterPropertiesSet() throws Exception { + assert ignite == null; + + if (cfgPath != null && cfg != null) { + throw new IllegalArgumentException("Both 'configurationPath' and 'configuration' are " + + "provided. Set only one of these properties if you need to start a Ignite node inside of " + + "SpringCacheManager. If you already have a node running, omit both of them and set" + + "'gridName' property."); + } + + if (cfgPath != null) + ignite = Ignition.start(cfgPath); + else if (cfg != null) + ignite = Ignition.start(cfg); + else + ignite = Ignition.ignite(gridName); + + if (transactionConcurrency == null) + transactionConcurrency = ignite.configuration().getTransactionConfiguration().getDefaultTxConcurrency(); + + log = ignite.log(); + } + + /** + * {@inheritDoc} + */ + @Override protected Object doGetTransaction() throws TransactionException { + IgniteTransactionObject txObject = new IgniteTransactionObject(); + + txObject.setTransactionHolder( + (IgniteTransactionHolder)TransactionSynchronizationManager.getResource(this.ignite), false); + + return txObject; + } + + /** + * {@inheritDoc} + */ + @Override protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { + if (definition.getIsolationLevel() == TransactionDefinition.ISOLATION_READ_UNCOMMITTED) + throw new InvalidIsolationLevelException("Ignite does not support READ_UNCOMMITTED isolation level."); + + IgniteTransactionObject txObject = (IgniteTransactionObject)transaction; + Transaction tx = null; + + try { + if (txObject.getTransactionHolder() == null || txObject.getTransactionHolder().isSynchronizedWithTransaction()) { + long timeout = ignite.configuration().getTransactionConfiguration().getDefaultTxTimeout(); + + if (definition.getTimeout() > 0) + timeout = TimeUnit.SECONDS.toMillis(definition.getTimeout()); + + Transaction newTx = ignite.transactions().txStart(transactionConcurrency, + convertToIgniteIsolationLevel(definition.getIsolationLevel()), timeout, 0); + + if (log.isDebugEnabled()) + log.debug("Started Ignite transaction: " + newTx); + + txObject.setTransactionHolder(new IgniteTransactionHolder(newTx), true); + } + + txObject.getTransactionHolder().setSynchronizedWithTransaction(true); + txObject.getTransactionHolder().setTransactionActive(true); + + tx = txObject.getTransactionHolder().getTransaction(); + + // Bind the session holder to the thread. + if (txObject.isNewTransactionHolder()) + TransactionSynchronizationManager.bindResource(this.ignite, txObject.getTransactionHolder()); + } + catch (Exception ex) { + if (tx != null) + tx.close(); + + throw new CannotCreateTransactionException("Could not create Ignite transaction", ex); + } + } + + /** + * {@inheritDoc} + */ + @Override protected void doCommit(DefaultTransactionStatus status) throws TransactionException { + IgniteTransactionObject txObject = (IgniteTransactionObject)status.getTransaction(); + Transaction tx = txObject.getTransactionHolder().getTransaction(); + + if (status.isDebug() && log.isDebugEnabled()) + log.debug("Committing Ignite transaction: " + tx); + + try { + tx.commit(); + } + catch (IgniteException e) { + throw new TransactionSystemException("Could not commit Ignite transaction", e); + } + } + + /** + * {@inheritDoc} + */ + @Override protected void doRollback(DefaultTransactionStatus status) throws TransactionException { + IgniteTransactionObject txObject = (IgniteTransactionObject)status.getTransaction(); + Transaction tx = txObject.getTransactionHolder().getTransaction(); + + if (status.isDebug() && log.isDebugEnabled()) + log.debug("Rolling back Ignite transaction: " + tx); + + try { + tx.rollback(); + } + catch (IgniteException e) { + throw new TransactionSystemException("Could not rollback Ignite transaction", e); + } + } + + /** + * {@inheritDoc} + */ + @Override protected void doCleanupAfterCompletion(Object transaction) { + IgniteTransactionObject txObject = (IgniteTransactionObject)transaction; + + // Remove the transaction holder from the thread, if exposed. + if (txObject.isNewTransactionHolder()) { + Transaction tx = txObject.getTransactionHolder().getTransaction(); + TransactionSynchronizationManager.unbindResource(this.ignite); + + if (log.isDebugEnabled()) + log.debug("Releasing Ignite transaction: " + tx); + } + + txObject.getTransactionHolder().clear(); + } + + /** + * {@inheritDoc} + */ + @Override protected boolean isExistingTransaction(Object transaction) throws TransactionException { + IgniteTransactionObject txObject = (IgniteTransactionObject)transaction; + + return (txObject.getTransactionHolder() != null && txObject.getTransactionHolder().isTransactionActive()); + } + + /** + * {@inheritDoc} + */ + @Override public Object getResourceFactory() { + return this.ignite; + } + + /** + * @param isolationLevel Spring isolation level. + * @return Ignite isolation level. + */ + private TransactionIsolation convertToIgniteIsolationLevel(int isolationLevel) { + TransactionIsolation isolation = ignite.configuration().getTransactionConfiguration().getDefaultTxIsolation(); + switch (isolationLevel) { + case TransactionDefinition.ISOLATION_READ_COMMITTED: + isolation = TransactionIsolation.READ_COMMITTED; + break; + case TransactionDefinition.ISOLATION_REPEATABLE_READ: + isolation = TransactionIsolation.REPEATABLE_READ; + break; + case TransactionDefinition.ISOLATION_SERIALIZABLE: + isolation = TransactionIsolation.SERIALIZABLE; + } + return isolation; + } + + /** + * An object representing a managed Ignite transaction. + */ + private static class IgniteTransactionObject { + /** */ + private IgniteTransactionHolder transactionHolder; + + /** */ + private boolean newTransactionHolder; + + /** + * Sets the resource holder being used to hold Ignite resources in the + * transaction. + * + * @param transactionHolder the transaction resource holder + * @param newHolder true if the holder was created for this transaction, + * false if it already existed + */ + private void setTransactionHolder(IgniteTransactionHolder transactionHolder, boolean newHolder) { + this.transactionHolder = transactionHolder; + this.newTransactionHolder = newHolder; + } + + /** + * Returns the resource holder being used to hold Ignite resources in the + * transaction. + * + * @return the transaction resource holder + */ + private IgniteTransactionHolder getTransactionHolder() { + return transactionHolder; + } + + /** + * Returns true if the transaction holder was created for the current + * transaction and false if it existed prior to the transaction. + * + * @return true if the holder was created for this transaction, false if it + * already existed + */ + private boolean isNewTransactionHolder() { + return newTransactionHolder; + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/457ca6fb/modules/spring/src/main/java/org/apache/ignite/transactions/spring/package-info.java ---------------------------------------------------------------------- diff --git a/modules/spring/src/main/java/org/apache/ignite/transactions/spring/package-info.java b/modules/spring/src/main/java/org/apache/ignite/transactions/spring/package-info.java new file mode 100644 index 0000000..66b9942 --- /dev/null +++ b/modules/spring/src/main/java/org/apache/ignite/transactions/spring/package-info.java @@ -0,0 +1,22 @@ +/* + * 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 description. --> + * Contains implementation of Spring transaction manager. + */ +package org.apache.ignite.transactions.spring; http://git-wip-us.apache.org/repos/asf/ignite/blob/457ca6fb/modules/spring/src/test/java/config/spring-transactions.xml ---------------------------------------------------------------------- diff --git a/modules/spring/src/test/java/config/spring-transactions.xml b/modules/spring/src/test/java/config/spring-transactions.xml new file mode 100644 index 0000000..ba90cb0 --- /dev/null +++ b/modules/spring/src/test/java/config/spring-transactions.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + 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. +--> + +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:context="http://www.springframework.org/schema/context" + xmlns:tx="http://www.springframework.org/schema/tx" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> + + <tx:annotation-driven/> + <context:component-scan base-package="org.apache.ignite.transactions.spring"/> + + <bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"> + <property name="transactionConcurrency" value="OPTIMISTIC"/> + <property name="gridName" value="testGrid"/> + </bean> +</beans> http://git-wip-us.apache.org/repos/asf/ignite/blob/457ca6fb/modules/spring/src/test/java/org/apache/ignite/testsuites/IgniteSpringTestSuite.java ---------------------------------------------------------------------- diff --git a/modules/spring/src/test/java/org/apache/ignite/testsuites/IgniteSpringTestSuite.java b/modules/spring/src/test/java/org/apache/ignite/testsuites/IgniteSpringTestSuite.java index 59e2490..c42c7e0 100644 --- a/modules/spring/src/test/java/org/apache/ignite/testsuites/IgniteSpringTestSuite.java +++ b/modules/spring/src/test/java/org/apache/ignite/testsuites/IgniteSpringTestSuite.java @@ -28,6 +28,7 @@ import org.apache.ignite.p2p.GridP2PUserVersionChangeSelfTest; import org.apache.ignite.spring.GridSpringCacheManagerSelfTest; import org.apache.ignite.spring.IgniteExcludeInConfigurationTest; import org.apache.ignite.spring.IgniteStartFromStreamConfigurationTest; +import org.apache.ignite.transactions.spring.GridSpringTransactionManagerSelfTest; /** * Spring tests. @@ -62,6 +63,8 @@ public class IgniteSpringTestSuite extends TestSuite { suite.addTestSuite(CacheJdbcPojoStoreFactorySelfTest.class); + suite.addTest(new TestSuite(GridSpringTransactionManagerSelfTest.class)); + return suite; } -} \ No newline at end of file +} http://git-wip-us.apache.org/repos/asf/ignite/blob/457ca6fb/modules/spring/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/spring/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerSelfTest.java b/modules/spring/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerSelfTest.java new file mode 100644 index 0000000..02d6e88 --- /dev/null +++ b/modules/spring/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerSelfTest.java @@ -0,0 +1,165 @@ +/* + * 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.apache.ignite.transactions.spring; + +import org.apache.ignite.IgniteCache; +import org.apache.ignite.cache.CacheAtomicityMode; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; +import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder; +import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.GenericXmlApplicationContext; +import org.springframework.transaction.IllegalTransactionStateException; +import org.springframework.transaction.InvalidIsolationLevelException; + +/** + * Spring transaction test. + */ +public class GridSpringTransactionManagerSelfTest extends GridCommonAbstractTest { + /** */ + private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true); + + /** */ + private static final String CACHE_NAME = "testCache"; + + /** */ + private GridSpringTransactionService service; + + /** + * {@inheritDoc} + */ + @Override + protected IgniteConfiguration getConfiguration(String gridName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(gridName); + + CacheConfiguration cache = new CacheConfiguration(); + + cache.setName(CACHE_NAME); + cache.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL); + + cfg.setCacheConfiguration(cache); + + TcpDiscoverySpi disco = new TcpDiscoverySpi(); + + disco.setIpFinder(IP_FINDER); + + cfg.setDiscoverySpi(disco); + + return cfg; + } + + /** + * {@inheritDoc} + */ + @Override + public String getTestGridName() { + return "testGrid"; + } + + /** + * {@inheritDoc} + */ + @Override + protected void beforeTestsStarted() throws Exception { + startGrid(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void afterTestsStopped() throws Exception { + stopAllGrids(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void beforeTest() throws Exception { + ApplicationContext applicationContext = new GenericXmlApplicationContext("config/spring-transactions.xml"); + + service = (GridSpringTransactionService)applicationContext.getBean("gridSpringTransactionService"); + } + + /** + * {@inheritDoc} + */ + @Override + protected void afterTest() throws Exception { + grid().cache(CACHE_NAME).removeAll(); + } + + /** */ + public void testSuccessPut() { + IgniteCache<Integer, String> c = grid().cache(CACHE_NAME); + + int entryCnt = 1_000; + + service.put(c, entryCnt); + + assertEquals(entryCnt, c.size()); + } + + /** */ + public void testFailPut() { + IgniteCache<Integer, String> c = grid().cache(CACHE_NAME); + + int entryCnt = 1_000; + + try { + service.putWithError(c, entryCnt); + } + catch (Exception e) { + // No-op. + } + + assertEquals(0, c.size()); + } + + /** */ + public void testMandatoryPropagation() { + IgniteCache<Integer, String> c = grid().cache(CACHE_NAME); + + try { + service.putWithMandatoryPropagation(c); + } + catch (IllegalTransactionStateException e) { + assertEquals("No existing transaction found for transaction marked with propagation 'mandatory'", e.getMessage()); + } + + assertEquals(0, c.size()); + } + + /** */ + public void testUnsupportedIsolationLevel() { + IgniteCache<Integer, String> c = grid().cache(CACHE_NAME); + + try { + service.putWithUnsupportedIsolationLevel(c); + } + catch (InvalidIsolationLevelException e) { + assertEquals("Ignite does not support READ_UNCOMMITTED isolation level.", e.getMessage()); + } + + assertEquals(0, c.size()); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/457ca6fb/modules/spring/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionService.java ---------------------------------------------------------------------- diff --git a/modules/spring/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionService.java b/modules/spring/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionService.java new file mode 100644 index 0000000..dc9bca7 --- /dev/null +++ b/modules/spring/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionService.java @@ -0,0 +1,68 @@ +/* + * 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.apache.ignite.transactions.spring; + +import org.apache.ignite.IgniteCache; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +/** + * Service. + */ +@Service +public class GridSpringTransactionService { + /** + * @param cache Cache. + * @param entryCnt Entries count. + */ + @Transactional + public void put(IgniteCache<Integer, String> cache, int entryCnt) { + for (int i = 0; i < entryCnt; i++) + cache.put(i, String.valueOf(i)); + } + + /** + * @param cache Cache. + * @param entryCnt Entries count. + */ + @Transactional + public void putWithError(IgniteCache<Integer, String> cache, int entryCnt) { + for (int i = 0; i < entryCnt; i++) + cache.put(i, String.valueOf(i)); + + cache.put(Integer.valueOf("one"), "one"); + } + + /** + * @param cache Cache. + */ + @Transactional(propagation = Propagation.MANDATORY) + public void putWithMandatoryPropagation(IgniteCache<Integer, String> cache) { + cache.put(1, "1"); + } + + /** + * @param cache Cache. + */ + @Transactional(isolation = Isolation.READ_UNCOMMITTED) + public void putWithUnsupportedIsolationLevel(IgniteCache<Integer, String> cache) { + cache.put(1, "1"); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/457ca6fb/parent/pom.xml ---------------------------------------------------------------------- diff --git a/parent/pom.xml b/parent/pom.xml index cb7c533..36dbdf4 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -324,8 +324,8 @@ <packages>org.apache.ignite.visor.plugin</packages> </group> <group> - <title>Spring Caching</title> - <packages>org.apache.ignite.cache.spring</packages> + <title>Spring Integration</title> + <packages>org.apache.ignite.cache.spring:org.apache.ignite.transactions.spring</packages> </group> <group> <title>Mesos Framework</title>