You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ag...@apache.org on 2015/11/21 13:02:29 UTC

[13/15] ignite git commit: Ignite-based Implementation of Spring tx manager

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-1282
Commit: 457ca6fb5d64faf88869f4db7525673d067002df
Parents: 2de3b19
Author: Amir Akhmedov <am...@gmail.com>
Authored: Fri Nov 20 19:03:14 2015 -0800
Committer: Valentin Kulichenko <va...@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">
+ * &lt;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"&gt;
+ *     &lt;-- Provide configuration file path. --&gt;
+ *     &lt;bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"&gt;
+ *         &lt;property name="configurationPath" value="examples/config/spring-transaction.xml"/&gt;
+ *     &lt;/bean&gt;
+ *
+ *     &lt;-- Use annotation-driven transaction configuration. --&gt;
+ *     &lt;tx:annotation-driven/&gt;
+ * &lt;/beans&gt;
+ * </pre>
+ * Or you can provide a {@link IgniteConfiguration} bean, like below:
+ * <pre name="code" class="xml">
+ * &lt;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"&gt;
+ *     &lt;-- Provide configuration bean. --&gt;
+ *     &lt;bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"&gt;
+ *         &lt;property name="configuration"&gt;
+ *             &lt;bean id="gridCfg" class="org.apache.ignite.configuration.IgniteConfiguration"&gt;
+ *                 ...
+ *             &lt;/bean&gt;
+ *         &lt;/property&gt;
+ *     &lt;/bean&gt;
+ *
+ *     &lt;-- Use annotation-driven transaction configuration. --&gt;
+ *     &lt;tx:annotation-driven/&gt;
+ * &lt;/beans&gt;
+ * </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">
+ * &lt;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"&gt;
+ *     &lt;-- Provide Grid name. --&gt;
+ *     &lt;bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"&gt;
+ *         &lt;property name="gridName" value="myGrid"/&gt;
+ *     &lt;/bean>
+ *
+ *     &lt;-- Use annotation-driven transaction configuration. --&gt;
+ *     &lt;tx:annotation-driven/&gt;
+ * &lt;/beans&gt;
+ * </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">
+ * &lt;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"&gt;
+ *     &lt;-- Provide Grid name. --&gt;
+ *     &lt;bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"&gt;
+ *         &lt;property name="gridName" value="myGrid"/&gt;
+ *         &lt;property name="transactionConcurrency" value="OPTIMISTIC"/&gt;
+ *     &lt;/bean>
+ *
+ *     &lt;-- Use annotation-driven transaction configuration. --&gt;
+ *     &lt;tx:annotation-driven/&gt;
+ * &lt;/beans&gt;
+ * </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">
+ * &lt;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"&gt;
+ *     &lt;bean id="optimisticTransactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"&gt;
+ *         &lt;property name="gridName" value="myGrid"/&gt;
+ *         &lt;property name="transactionConcurrency" value="OPTIMISTIC"/&gt;
+ *     &lt;/bean>
+ *
+ *     &lt;bean id="pessimisticTransactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager"&gt;
+ *         &lt;property name="gridName" value="myGrid"/&gt;
+ *         &lt;property name="transactionConcurrency" value="PESSIMISTIC"/&gt;
+ *     &lt;/bean>
+ *
+ *     &lt;-- Use annotation-driven transaction configuration. --&gt;
+ *     &lt;tx:annotation-driven/&gt;
+ * &lt;/beans&gt;
+ * </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>