Quellcode durchsuchen

mybatis 同一SqlSessionFactory,同一个Mapper,事务中数据源动态切换

zhouhao vor 8 Jahren
Ursprung
Commit
89516b109e

+ 14 - 4
hsweb-web-dao/hsweb-web-dao-mybatis/src/main/java/org/hsweb/web/mybatis/MyBatisAutoConfiguration.java

@@ -19,8 +19,11 @@ package org.hsweb.web.mybatis;
 import org.apache.ibatis.mapping.DatabaseIdProvider;
 import org.apache.ibatis.mapping.DatabaseIdProvider;
 import org.apache.ibatis.plugin.Interceptor;
 import org.apache.ibatis.plugin.Interceptor;
 import org.apache.ibatis.session.SqlSessionFactory;
 import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
 import org.hsweb.web.mybatis.dynamic.DynamicDataSourceSqlSessionFactoryBuilder;
 import org.hsweb.web.mybatis.dynamic.DynamicDataSourceSqlSessionFactoryBuilder;
+import org.hsweb.web.mybatis.dynamic.DynamicSqlSessionTemplate;
 import org.mybatis.spring.SqlSessionFactoryBean;
 import org.mybatis.spring.SqlSessionFactoryBean;
+import org.mybatis.spring.SqlSessionTemplate;
 import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
 import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -54,15 +57,14 @@ public class MyBatisAutoConfiguration {
     @Bean(name = "sqlSessionFactory")
     @Bean(name = "sqlSessionFactory")
     public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
     public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
         SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
         SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
-
+//        factory.setTransactionFactory(new ManagedTransactionFactory());
         if (properties.isDynamicDatasource())
         if (properties.isDynamicDatasource())
             factory.setSqlSessionFactoryBuilder(new DynamicDataSourceSqlSessionFactoryBuilder());
             factory.setSqlSessionFactoryBuilder(new DynamicDataSourceSqlSessionFactoryBuilder());
-
         factory.setDataSource(dataSource);
         factory.setDataSource(dataSource);
         factory.setVfs(SpringBootVFS.class);
         factory.setVfs(SpringBootVFS.class);
-        if (StringUtils.hasText(this.properties.getConfigLocation())) {
+        if (StringUtils.hasText(this.properties.getConfig())) {
             factory.setConfigLocation(this.resourceLoader.getResource(this.properties
             factory.setConfigLocation(this.resourceLoader.getResource(this.properties
-                    .getConfigLocation()));
+                    .getConfig()));
         }
         }
         if (this.interceptors != null && this.interceptors.length > 0) {
         if (this.interceptors != null && this.interceptors.length > 0) {
             factory.setPlugins(this.interceptors);
             factory.setPlugins(this.interceptors);
@@ -80,4 +82,12 @@ public class MyBatisAutoConfiguration {
         return factory.getObject();
         return factory.getObject();
     }
     }
 
 
+    @Bean(name = "sqlSessionTemplate")
+    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
+        if (this.properties.isDynamicDatasource()) {
+            return new DynamicSqlSessionTemplate(sqlSessionFactory);
+        }
+        return new SqlSessionTemplate(sqlSessionFactory);
+    }
+
 }
 }

+ 80 - 0
hsweb-web-dao/hsweb-web-dao-mybatis/src/main/java/org/hsweb/web/mybatis/dynamic/DynamicSqlSessionHolder.java

@@ -0,0 +1,80 @@
+/**
+ * Copyright 2010-2015 the original author or authors.
+ * <p>
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.hsweb.web.mybatis.dynamic;
+
+import org.apache.ibatis.session.SqlSession;
+import org.hsweb.web.core.datasource.DynamicDataSource;
+import org.springframework.transaction.support.ResourceHolderSupport;
+
+import java.util.*;
+
+public final class DynamicSqlSessionHolder extends ResourceHolderSupport {
+
+    private Map<String, SqlSession> sqlSessionMap;
+
+    public DynamicSqlSessionHolder() {
+        sqlSessionMap = new HashMap<>();
+    }
+
+    public SqlSession getSqlSession() {
+        return sqlSessionMap.get(getDataSourceId());
+    }
+
+    public void remove() {
+        sqlSessionMap.remove(getDataSourceId());
+    }
+
+    public void remove(String dataSourceId) {
+        sqlSessionMap.remove(dataSourceId);
+    }
+
+    public Collection<SqlSession> getAllSqlSession() {
+        return sqlSessionMap.values();
+    }
+
+    public void close() {
+        List<Exception> exceptions = new ArrayList<>();
+        sqlSessionMap.forEach((id, sqlSession) -> {
+            try {
+                sqlSession.close();
+            } catch (Exception e) {
+                exceptions.add(e);
+            }
+        });
+        sqlSessionMap.clear();
+        //todo 异常未处理
+        //   if (exceptions.size() > 0) throw new RuntimeException(exceptions.get(0));
+    }
+
+    public void commit() {
+        sqlSessionMap.values().forEach(SqlSession::commit);
+    }
+
+    public void setSqlSession(SqlSession sqlSession) {
+        sqlSessionMap.put(getDataSourceId(), sqlSession);
+    }
+
+
+    public void setSqlSession(String dataSourceId, SqlSession sqlSession) {
+        sqlSessionMap.put(dataSourceId, sqlSession);
+    }
+
+    public String getDataSourceId() {
+        String id = DynamicDataSource.getActiveDataSourceId();
+        if (null == id) return "default";
+        return id;
+    }
+}

+ 372 - 0
hsweb-web-dao/hsweb-web-dao-mybatis/src/main/java/org/hsweb/web/mybatis/dynamic/DynamicSqlSessionTemplate.java

@@ -0,0 +1,372 @@
+/**
+ * Copyright 2010-2015 the original author or authors.
+ * <p>
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.hsweb.web.mybatis.dynamic;
+
+import org.apache.ibatis.exceptions.PersistenceException;
+import org.apache.ibatis.executor.BatchResult;
+import org.apache.ibatis.session.*;
+import org.mybatis.spring.MyBatisExceptionTranslator;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.dao.support.PersistenceExceptionTranslator;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.sql.Connection;
+import java.util.List;
+import java.util.Map;
+
+import static java.lang.reflect.Proxy.newProxyInstance;
+import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;
+import static org.hsweb.web.mybatis.dynamic.DynamicSqlSessionUtils.*;
+import static org.springframework.util.Assert.notNull;
+
+public class DynamicSqlSessionTemplate extends SqlSessionTemplate implements SqlSession, DisposableBean {
+
+    private final SqlSessionFactory sqlSessionFactory;
+
+    private final ExecutorType executorType;
+
+    private final SqlSession sqlSessionProxy;
+
+    private final PersistenceExceptionTranslator exceptionTranslator;
+
+    /**
+     * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory}
+     * provided as an argument.
+     *
+     * @param sqlSessionFactory
+     */
+    public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
+        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
+    }
+
+    /**
+     * Constructs a Spring managed SqlSession with the {@code SqlSessionFactory}
+     * provided as an argument and the given {@code ExecutorType}
+     * {@code ExecutorType} cannot be changed once the {@code SqlSessionTemplate}
+     * is constructed.
+     *
+     * @param sqlSessionFactory
+     * @param executorType
+     */
+    public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
+        this(sqlSessionFactory, executorType,
+                new MyBatisExceptionTranslator(
+                        sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
+    }
+
+    /**
+     * Constructs a Spring managed {@code SqlSession} with the given
+     * {@code SqlSessionFactory} and {@code ExecutorType}.
+     * A custom {@code SQLExceptionTranslator} can be provided as an
+     * argument so any {@code PersistenceException} thrown by MyBatis
+     * can be custom translated to a {@code RuntimeException}
+     * The {@code SQLExceptionTranslator} can also be null and thus no
+     * exception translation will be done and MyBatis exceptions will be
+     * thrown
+     *
+     * @param sqlSessionFactory
+     * @param executorType
+     * @param exceptionTranslator
+     */
+    public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
+                                     PersistenceExceptionTranslator exceptionTranslator) {
+        super(sqlSessionFactory, executorType);
+
+        notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
+        notNull(executorType, "Property 'executorType' is required");
+
+        this.sqlSessionFactory = sqlSessionFactory;
+        this.executorType = executorType;
+        this.exceptionTranslator = exceptionTranslator;
+        this.sqlSessionProxy = (SqlSession) newProxyInstance(
+                SqlSessionFactory.class.getClassLoader(),
+                new Class[]{SqlSession.class},
+                new SqlSessionInterceptor());
+    }
+
+    public SqlSessionFactory getSqlSessionFactory() {
+        return this.sqlSessionFactory;
+    }
+
+    public ExecutorType getExecutorType() {
+        return this.executorType;
+    }
+
+    public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
+        return this.exceptionTranslator;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T> T selectOne(String statement) {
+        return this.sqlSessionProxy.<T>selectOne(statement);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T> T selectOne(String statement, Object parameter) {
+        return this.sqlSessionProxy.<T>selectOne(statement, parameter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
+        return this.sqlSessionProxy.<K, V>selectMap(statement, mapKey);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
+        return this.sqlSessionProxy.<K, V>selectMap(statement, parameter, mapKey);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
+        return this.sqlSessionProxy.<K, V>selectMap(statement, parameter, mapKey, rowBounds);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <E> List<E> selectList(String statement) {
+        return this.sqlSessionProxy.<E>selectList(statement);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <E> List<E> selectList(String statement, Object parameter) {
+        return this.sqlSessionProxy.<E>selectList(statement, parameter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
+        return this.sqlSessionProxy.<E>selectList(statement, parameter, rowBounds);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void select(String statement, ResultHandler handler) {
+        this.sqlSessionProxy.select(statement, handler);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void select(String statement, Object parameter, ResultHandler handler) {
+        this.sqlSessionProxy.select(statement, parameter, handler);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
+        this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int insert(String statement) {
+        return this.sqlSessionProxy.insert(statement);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int insert(String statement, Object parameter) {
+        return this.sqlSessionProxy.insert(statement, parameter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int update(String statement) {
+        return this.sqlSessionProxy.update(statement);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int update(String statement, Object parameter) {
+        return this.sqlSessionProxy.update(statement, parameter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int delete(String statement) {
+        return this.sqlSessionProxy.delete(statement);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int delete(String statement, Object parameter) {
+        return this.sqlSessionProxy.delete(statement, parameter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T> T getMapper(Class<T> type) {
+        return getConfiguration().getMapper(type, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void commit() {
+        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void commit(boolean force) {
+        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void rollback() {
+        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void rollback(boolean force) {
+        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close() {
+        throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clearCache() {
+        this.sqlSessionProxy.clearCache();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Configuration getConfiguration() {
+        return this.sqlSessionFactory.getConfiguration();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Connection getConnection() {
+        return this.sqlSessionProxy.getConnection();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 1.0.2
+     */
+    @Override
+    public List<BatchResult> flushStatements() {
+        return this.sqlSessionProxy.flushStatements();
+    }
+
+    @Override
+    public void destroy() throws Exception {
+    }
+
+    private class SqlSessionInterceptor implements InvocationHandler {
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            SqlSession sqlSession = getSqlSession(
+                    DynamicSqlSessionTemplate.this.sqlSessionFactory,
+                    DynamicSqlSessionTemplate.this.executorType,
+                    DynamicSqlSessionTemplate.this.exceptionTranslator);
+
+            try {
+                Object result = method.invoke(sqlSession, args);
+                if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.sqlSessionFactory)) {
+                    // force commit even on non-dirty sessions because some databases require
+                    // a commit/rollback before calling close()
+                    sqlSession.commit(true);
+                }
+                return result;
+            } catch (Throwable t) {
+                Throwable unwrapped = unwrapThrowable(t);
+                if (DynamicSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
+                    // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
+                    closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.sqlSessionFactory);
+                    sqlSession = null;
+                    Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
+                    if (translated != null) {
+                        unwrapped = translated;
+                    }
+                }
+                throw unwrapped;
+            } finally {
+                if (sqlSession != null) {
+                    closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.sqlSessionFactory);
+                }
+            }
+        }
+    }
+
+}

+ 293 - 0
hsweb-web-dao/hsweb-web-dao-mybatis/src/main/java/org/hsweb/web/mybatis/dynamic/DynamicSqlSessionUtils.java

@@ -0,0 +1,293 @@
+/**
+ * Copyright 2010-2015 the original author or authors.
+ * <p>
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.hsweb.web.mybatis.dynamic;
+
+import static org.springframework.util.Assert.notNull;
+
+import org.apache.ibatis.exceptions.PersistenceException;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+import org.apache.ibatis.mapping.Environment;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.hsweb.web.core.datasource.DataSourceHolder;
+import org.mybatis.spring.SqlSessionHolder;
+import org.mybatis.spring.SqlSessionUtils;
+import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.TransientDataAccessResourceException;
+import org.springframework.dao.support.PersistenceExceptionTranslator;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.transaction.support.TransactionSynchronizationAdapter;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+import javax.sql.DataSource;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Handles MyBatis SqlSession life cycle. It can register and get SqlSessions from
+ * Spring {@code TransactionSynchronizationManager}. Also works if no transaction is active.
+ *
+ * @author Hunter Presnall
+ * @author Eduardo Macarron
+ * @version $Id$
+ */
+public final class DynamicSqlSessionUtils {
+
+    private static final Log LOGGER = LogFactory.getLog(DynamicSqlSessionUtils.class);
+
+    private static final String NO_EXECUTOR_TYPE_SPECIFIED       = "No ExecutorType specified";
+    private static final String NO_SQL_SESSION_FACTORY_SPECIFIED = "No SqlSessionFactory specified";
+    private static final String NO_SQL_SESSION_SPECIFIED         = "No SqlSession specified";
+
+    /**
+     * This class can't be instantiated, exposes static utility methods only.
+     */
+    private DynamicSqlSessionUtils() {
+        // do nothing
+    }
+
+    /**
+     * Creates a new MyBatis {@code SqlSession} from the {@code SqlSessionFactory}
+     * provided as a parameter and using its {@code DataSource} and {@code ExecutorType}
+     *
+     * @param sessionFactory a MyBatis {@code SqlSessionFactory} to create new sessions
+     * @return a MyBatis {@code SqlSession}
+     * @throws TransientDataAccessResourceException if a transaction is active and the
+     *                                              {@code SqlSessionFactory} is not using a {@code SpringManagedTransactionFactory}
+     */
+    public static SqlSession getSqlSession(SqlSessionFactory sessionFactory) {
+        ExecutorType executorType = sessionFactory.getConfiguration().getDefaultExecutorType();
+        return getSqlSession(sessionFactory, executorType, null);
+    }
+
+    static final String SQL_SESSION_RESOURCE_KEY = "dynamic-sqlSession";
+
+    /**
+     * Gets an SqlSession from Spring Transaction Manager or creates a new one if needed.
+     * Tries to get a SqlSession out of current transaction. If there is not any, it creates a new one.
+     * Then, it synchronizes the SqlSession with the transaction if Spring TX is active and
+     * <code>SpringManagedTransactionFactory</code> is configured as a transaction manager.
+     *
+     * @param sessionFactory      a MyBatis {@code SqlSessionFactory} to create new sessions
+     * @param executorType        The executor type of the SqlSession to create
+     * @param exceptionTranslator Optional. Translates SqlSession.commit() exceptions to Spring exceptions.
+     * @throws TransientDataAccessResourceException if a transaction is active and the
+     *                                              {@code SqlSessionFactory} is not using a {@code SpringManagedTransactionFactory}
+     * @see SpringManagedTransactionFactory
+     */
+    public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
+
+        notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
+        notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
+        DynamicSqlSessionHolder holder = (DynamicSqlSessionHolder) TransactionSynchronizationManager.getResource(SQL_SESSION_RESOURCE_KEY);
+        if (holder == null) {
+            TransactionSynchronizationManager.bindResource(SQL_SESSION_RESOURCE_KEY, holder = new DynamicSqlSessionHolder());
+        }
+        SqlSession session = holder.getSqlSession();
+        if (session != null) {
+            return session;
+        }
+
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("Creating a new SqlSession for datasource " + holder.getDataSourceId());
+        }
+
+        session = sessionFactory.openSession(executorType);
+        if (TransactionSynchronizationManager.isSynchronizationActive()) {
+            TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(session, holder));
+            if (!holder.isSynchronizedWithTransaction()) {
+                holder.setSynchronizedWithTransaction(true);
+                holder.requested();
+            }
+
+        }
+        holder.setSqlSession(session);
+        return session;
+    }
+
+    /**
+     * Checks if {@code SqlSession} passed as an argument is managed by Spring {@code TransactionSynchronizationManager}
+     * If it is not, it closes it, otherwise it just updates the reference counter and
+     * lets Spring call the close callback when the managed transaction ends
+     *
+     * @param session
+     * @param sessionFactory
+     */
+    public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
+        notNull(session, NO_SQL_SESSION_SPECIFIED);
+        notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
+        DynamicSqlSessionHolder holder = (DynamicSqlSessionHolder) TransactionSynchronizationManager.getResource(SQL_SESSION_RESOURCE_KEY);
+        if ((holder != null) && (holder.getAllSqlSession().contains(session))) {
+            if (LOGGER.isDebugEnabled()) {
+                LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
+            }
+            holder.released();
+        } else {
+            if (LOGGER.isDebugEnabled()) {
+                LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
+            }
+            session.close();
+        }
+    }
+
+    /**
+     * Returns if the {@code SqlSession} passed as an argument is being managed by Spring
+     *
+     * @param session        a MyBatis SqlSession to check
+     * @param sessionFactory the SqlSessionFactory which the SqlSession was built with
+     * @return true if session is transactional, otherwise false
+     */
+    public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {
+        notNull(session, NO_SQL_SESSION_SPECIFIED);
+        notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
+        DynamicSqlSessionHolder holder = (DynamicSqlSessionHolder) TransactionSynchronizationManager.getResource(SQL_SESSION_RESOURCE_KEY);
+
+        return (holder != null) && (holder.getAllSqlSession().contains(session));
+    }
+
+    /**
+     * Callback for cleaning up resources. It cleans TransactionSynchronizationManager and
+     * also commits and closes the {@code SqlSession}.
+     * It assumes that {@code Connection} life cycle will be managed by
+     * {@code DataSourceTransactionManager} or {@code JtaTransactionManager}
+     */
+    private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter {
+
+        private final DynamicSqlSessionHolder holder;
+
+        private boolean holderActive = true;
+
+        private SqlSession sqlSession;
+
+        private String dataSourceId;
+
+        public SqlSessionSynchronization(SqlSession sqlSession, DynamicSqlSessionHolder holder) {
+            notNull(holder, "Parameter 'holder' must be not null");
+            this.holder = holder;
+            this.sqlSession = sqlSession;
+            this.dataSourceId = holder.getDataSourceId();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getOrder() {
+            // order right before any Connection synchronization
+            return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 1;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void suspend() {
+            if (this.holderActive) {
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("Transaction synchronization suspending SqlSession [" + sqlSession + "] for dataSource :" + dataSourceId);
+                }
+                holder.remove(dataSourceId);
+                if (holder.getAllSqlSession().isEmpty() && TransactionSynchronizationManager.getResource(SQL_SESSION_RESOURCE_KEY) != null)
+                    TransactionSynchronizationManager.unbindResource(SQL_SESSION_RESOURCE_KEY);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void resume() {
+            if (this.holderActive) {
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("Transaction synchronization resuming SqlSession [" + sqlSession + "] for dataSource :" + dataSourceId);
+                }
+                holder.setSqlSession(dataSourceId, sqlSession);
+                if (TransactionSynchronizationManager.getResource(SQL_SESSION_RESOURCE_KEY) == null)
+                    TransactionSynchronizationManager.bindResource(SQL_SESSION_RESOURCE_KEY, this.holder);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void beforeCommit(boolean readOnly) {
+            // Connection commit or rollback will be handled by ConnectionSynchronization or
+            // DataSourceTransactionManager.
+            // But, do cleanup the SqlSession / Executor, including flushing BATCH statements so
+            // they are actually executed.
+            // SpringManagedTransaction will no-op the commit over the jdbc connection
+            // TODO This updates 2nd level caches but the tx may be rolledback later on!
+            if (TransactionSynchronizationManager.isActualTransactionActive()) {
+                try {
+                    if (LOGGER.isDebugEnabled()) {
+                        LOGGER.debug("Transaction synchronization committing SqlSession [" + sqlSession + "] for dataSource :" + dataSourceId);
+                    }
+                    sqlSession.commit();
+                } catch (PersistenceException p) {
+                    throw p;
+                }
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void beforeCompletion() {
+            // Issue #18 Close SqlSession and deregister it now
+            // because afterCompletion may be called from a different thread
+            if (!this.holder.isOpen()) {
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("Transaction synchronization deregistering SqlSession [" + sqlSession + "] for dataSource :" + dataSourceId);
+                }
+                holder.remove(dataSourceId);
+                if (holder.getAllSqlSession().isEmpty() && TransactionSynchronizationManager.getResource(SQL_SESSION_RESOURCE_KEY) != null)
+                    TransactionSynchronizationManager.unbindResource(SQL_SESSION_RESOURCE_KEY);
+                this.holderActive = false;
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("Transaction synchronization closing SqlSession [" + sqlSession + "] for dataSource :" + dataSourceId);
+                }
+                sqlSession.close();
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void afterCompletion(int status) {
+            if (this.holderActive) {
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("Transaction synchronization deregistering SqlSession [" + sqlSession + "] for dataSource :" + dataSourceId);
+                }
+                holder.remove(dataSourceId);
+                if (holder.getAllSqlSession().isEmpty() && TransactionSynchronizationManager.getResource(SQL_SESSION_RESOURCE_KEY) != null)
+                    TransactionSynchronizationManager.unbindResource(SQL_SESSION_RESOURCE_KEY);
+                this.holderActive = false;
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.debug("Transaction synchronization closing SqlSession [" + sqlSession + "] for dataSource :" + dataSourceId);
+                }
+                sqlSession.close();
+            }
+            //this.holder.reset();
+        }
+    }
+
+}