一、摘要

上两篇文章分别介绍了Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法Spring3 整合Hibernate3.5 动态切换SessionFactory (切换数据库方言),这篇文章将介绍Spring整合Mybatis 如何完成SqlSessionFactory的动态切换的。并且会简单的介绍下MyBatis整合Spring中的官方的相关代码。

Spring整合MyBatis切换SqlSessionFactory有两种方法,第一、 继承SqlSessionDaoSupport,重写获取SqlSessionFactory的方法。第二、继承SqlSessionTemplate 重写getSqlSessionFactory、getConfiguration和SqlSessionInterceptor这个拦截器。其中最为关键还是继承SqlSessionTemplate 并重写里面的方法。

而Spring整合MyBatis也有两种方式,一种是配置MapperFactoryBean,另一种则是利用MapperScannerConfigurer进行扫描接口或包完成对象的自动创建。相对来说后者更方便些。MapperFactoryBean继承了SqlSessionDaoSupport也就是动态切换SqlSessionFactory的第一种方法,我们需要重写和实现SqlSessionDaoSupport方法,或者是继承MapperFactoryBean来重写覆盖相关方法。如果利用MapperScannerConfigurer的配置整合来切换SqlSessionFactory,那么我们就需要继承SqlSessionTemplate,重写上面提到的方法。在整合的配置中很多地方都是可以注入SqlSessionTemplate代替SqlSessionFactory的注入的。因为SqlSessionTemplate的创建也是需要注入SqlSessionFactory的。

 

二、实现代码

1、继承SqlSessionTemplate 重写getSqlSessionFactory、getConfiguration和SqlSessionInterceptor

package com.hoo.framework.mybatis.support;

 

import static java.lang.reflect.Proxy.newProxyInstance;

import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;

import static org.mybatis.spring.SqlSessionUtils.closeSqlSession;

import static org.mybatis.spring.SqlSessionUtils.getSqlSession;

import static org.mybatis.spring.SqlSessionUtils.isSqlSessionTransactional;

 

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.sql.Connection;

import java.util.List;

import java.util.Map;

 

import org.apache.ibatis.exceptions.PersistenceException;

import org.apache.ibatis.executor.BatchResult;

import org.apache.ibatis.session.Configuration;

import org.apache.ibatis.session.ExecutorType;

import org.apache.ibatis.session.ResultHandler;

import org.apache.ibatis.session.RowBounds;

import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.MyBatisExceptionTranslator;

import org.mybatis.spring.SqlSessionTemplate;

import org.springframework.dao.support.PersistenceExceptionTranslator;

import org.springframework.util.Assert;

 

/**

 * <b>function:</b> 继承SqlSessionTemplate 重写相关方法

 * @author hoojo

 * @createDate 2013-10-18 下午03:07:46

 * @file CustomSqlSessionTemplate.java

 * @package com.hoo.framework.mybatis.support

 * @project SHMB

 * @blog http://blog.csdn.net/IBM_hoojo

 * @email hoojo_@126.com

 * @version 1.0

 */

public class CustomSqlSessionTemplate extends SqlSessionTemplate {

 

    private final SqlSessionFactory sqlSessionFactory;

    private final ExecutorType executorType;

    private final SqlSession sqlSessionProxy;

    private final PersistenceExceptionTranslator exceptionTranslator;

 

    private Map<Object, SqlSessionFactory> targetSqlSessionFactorys;

    private SqlSessionFactory defaultTargetSqlSessionFactory;

 

    public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) {

        this.targetSqlSessionFactorys = targetSqlSessionFactorys;

    }

 

    public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {

        this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;

    }

 

    public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {

        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());

    }

 

    public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {

        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration()

                .getEnvironment().getDataSource(), true));

    }

 

    public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,

            PersistenceExceptionTranslator exceptionTranslator) {

 

        super(sqlSessionFactory, executorType, exceptionTranslator);

 

        this.sqlSessionFactory = sqlSessionFactory;

        this.executorType = executorType;

        this.exceptionTranslator = exceptionTranslator;

        

        this.sqlSessionProxy = (SqlSession) newProxyInstance(

                SqlSessionFactory.class.getClassLoader(),

                new Class[] { SqlSession.class }, 

                new SqlSessionInterceptor());

 

        this.defaultTargetSqlSessionFactory = sqlSessionFactory;

    }

 

    @Override

    public SqlSessionFactory getSqlSessionFactory() {

 

        SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(CustomerContextHolder.getContextType());

        if (targetSqlSessionFactory != null) {

            return targetSqlSessionFactory;

        } else if (defaultTargetSqlSessionFactory != null) {

            return defaultTargetSqlSessionFactory;

        } else {

            Assert.notNull(targetSqlSessionFactorys, "Property 'targetSqlSessionFactorys' or 'defaultTargetSqlSessionFactory' are required");

            Assert.notNull(defaultTargetSqlSessionFactory, "Property 'defaultTargetSqlSessionFactory' or 'targetSqlSessionFactorys' are required");

        }

        return this.sqlSessionFactory;

    }

 

    @Override

    public Configuration getConfiguration() {

        return this.getSqlSessionFactory().getConfiguration();

    }

 

    public ExecutorType getExecutorType() {

        return this.executorType;

    }

 

    public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {

        return this.exceptionTranslator;

    }

 

    /**

     * {@inheritDoc}

     */

    public <T> T selectOne(String statement) {

        return this.sqlSessionProxy.<T> selectOne(statement);

    }

 

    /**

     * {@inheritDoc}

     */

    public <T> T selectOne(String statement, Object parameter) {

        return this.sqlSessionProxy.<T> selectOne(statement, parameter);

    }

 

    /**

     * {@inheritDoc}

     */

    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {

        return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey);

    }

 

    /**

     * {@inheritDoc}

     */

    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {

        return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey);

    }

 

    /**

     * {@inheritDoc}

     */

    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}

     */

    public <E> List<E> selectList(String statement) {

        return this.sqlSessionProxy.<E> selectList(statement);

    }

 

    /**

     * {@inheritDoc}

     */

    public <E> List<E> selectList(String statement, Object parameter) {

        return this.sqlSessionProxy.<E> selectList(statement, parameter);

    }

 

    /**

     * {@inheritDoc}

     */

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {

        return this.sqlSessionProxy.<E> selectList(statement, parameter, rowBounds);

    }

 

    /**

     * {@inheritDoc}

     */

    public void select(String statement, ResultHandler handler) {

        this.sqlSessionProxy.select(statement, handler);

    }

 

    /**

     * {@inheritDoc}

     */

    public void select(String statement, Object parameter, ResultHandler handler) {

        this.sqlSessionProxy.select(statement, parameter, handler);

    }

 

    /**

     * {@inheritDoc}

     */

    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {

        this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);

    }

 

    /**

     * {@inheritDoc}

     */

    public int insert(String statement) {

        return this.sqlSessionProxy.insert(statement);

    }

 

    /**

     * {@inheritDoc}

     */

    public int insert(String statement, Object parameter) {

        return this.sqlSessionProxy.insert(statement, parameter);

    }

 

    /**

     * {@inheritDoc}

     */

    public int update(String statement) {

        return this.sqlSessionProxy.update(statement);

    }

 

    /**

     * {@inheritDoc}

     */

    public int update(String statement, Object parameter) {

        return this.sqlSessionProxy.update(statement, parameter);

    }

 

    /**

     * {@inheritDoc}

     */

    public int delete(String statement) {

        return this.sqlSessionProxy.delete(statement);

    }

 

    /**

     * {@inheritDoc}

     */

    public int delete(String statement, Object parameter) {

        return this.sqlSessionProxy.delete(statement, parameter);

    }

 

    /**

     * {@inheritDoc}

     */

    public <T> T getMapper(Class<T> type) {

        return getConfiguration().getMapper(type, this);

    }

 

    /**

     * {@inheritDoc}

     */

    public void commit() {

        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");

    }

 

    /**

     * {@inheritDoc}

     */

    public void commit(boolean force) {

        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");

    }

 

    /**

     * {@inheritDoc}

     */

    public void rollback() {

        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");

    }

 

    /**

     * {@inheritDoc}

     */

    public void rollback(boolean force) {

        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");

    }

 

    /**

     * {@inheritDoc}

     */

    public void close() {

        throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");

    }

 

    /**

     * {@inheritDoc}

     */

    public void clearCache() {

        this.sqlSessionProxy.clearCache();

    }

 

    /**

     * {@inheritDoc}

     */

    public Connection getConnection() {

        return this.sqlSessionProxy.getConnection();

    }

 

    /**

     * {@inheritDoc}

     * @since 1.0.2

     */

    public List<BatchResult> flushStatements() {

        return this.sqlSessionProxy.flushStatements();

    }

 

    /**

     * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also

     * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to

     * the {@code PersistenceExceptionTranslator}.

     */

    private class SqlSessionInterceptor implements InvocationHandler {

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            final SqlSession sqlSession = getSqlSession(

                    CustomSqlSessionTemplate.this.getSqlSessionFactory(),

                    CustomSqlSessionTemplate.this.executorType, 

                    CustomSqlSessionTemplate.this.exceptionTranslator);

            try {

                Object result = method.invoke(sqlSession, args);

                if (!isSqlSessionTransactional(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory())) {

                    // 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 (CustomSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {

                    Throwable translated = CustomSqlSessionTemplate.this.exceptionTranslator

                        .translateExceptionIfPossible((PersistenceException) unwrapped);

                    if (translated != null) {

                        unwrapped = translated;

                    }

                }

                throw unwrapped;

            } finally {

                closeSqlSession(sqlSession, CustomSqlSessionTemplate.this.getSqlSessionFactory());

            }

        }

    }

 

}

重写后的getSqlSessionFactory方法会从我们配置的SqlSessionFactory集合targetSqlSessionFactorys或默认的defaultTargetSqlSessionFactory中获取Session对象。而改写的SqlSessionInterceptor 是这个MyBatis整合Spring的关键,所有的SqlSessionFactory对象的session都将在这里完成创建、提交、关闭等操作。所以我们改写这里的代码,在这里获取getSqlSessionFactory的时候,从多个SqlSessionFactory中获取我们设置的那个即可。

上面添加了targetSqlSessionFactorys、defaultTargetSqlSessionFactory两个属性来配置多个SqlSessionFactory对象和默认的SqlSessionFactory对象。

 

CustomerContextHolder 设置SqlSessionFactory的类型

package com.hoo.framework.mybatis.support;

 

/**

 * <b>function:</b> 多数据源

 * @author hoojo

 * @createDate 2013-9-27 上午11:36:57

 * @file CustomerContextHolder.java

 * @package com.hoo.framework.spring.support

 * @project SHMB

 * @blog http://blog.csdn.net/IBM_hoojo

 * @email hoojo_@126.com

 * @version 1.0

 */

public abstract class CustomerContextHolder {

 

    public final static String SESSION_FACTORY_MYSQL = "mysql";

    public final static String SESSION_FACTORY_ORACLE = "oracle";

    

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();  

    

    public static void setContextType(String contextType) {  

        contextHolder.set(contextType);  

    }  

      

    public static String getContextType() {  

        return contextHolder.get();  

    }  

      

    public static void clearContextType() {  

        contextHolder.remove();  

    }  

}

 

2、配置相关的文件applicationContext-session-factory.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:aop="http://www.springframework.org/schema/aop" 

    xmlns:tx="http://www.springframework.org/schema/tx"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://www.springframework.org/schema/beans 

    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 

    http://www.springframework.org/schema/aop 

    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 

    http://www.springframework.org/schema/tx  

    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

    

    <!-- 配置c3p0数据源 -->

    <bean id="dataSourceOracle" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">

        <property name="driverClass" value="${datasource.driver}"/>

        <property name="jdbcUrl" value="${datasource.url}"/>

        <property name="user" value="${datasource.username}"/>

        <property name="password" value="${datasource.password}"/>

                

        <property name="acquireIncrement" value="${c3p0.acquireIncrement}"/>

        <property name="initialPoolSize" value="${c3p0.initialPoolSize}"/>

        <property name="minPoolSize" value="${c3p0.minPoolSize}"/>

        <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>

        <property name="maxIdleTime" value="${c3p0.maxIdleTime}"/>

        <property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}"/>

        <property name="maxStatements" value="${c3p0.maxStatements}"/>

        <property name="numHelperThreads" value="${c3p0.numHelperThreads}"/>

        <property name="preferredTestQuery" value="${c3p0.preferredTestQuery}"/>

        <property name="testConnectionOnCheckout" value="${c3p0.testConnectionOnCheckout}"/>

    </bean>

    

    <bean id="dataSourceMySQL" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">

        <property name="driverClass" value="com.mysql.jdbc.Driver"/>

        <property name="jdbcUrl" value="jdbc:mysql://172.31.108.178:3306/world?useUnicode=true&amp;characterEncoding=UTF-8&amp;zeroDateTimeBehavior=convertToNull"/>

        <property name="user" value="root"/>

        <property name="password" value="jp2011"/>

                

        <property name="acquireIncrement" value="${c3p0.acquireIncrement}"/>

        <property name="initialPoolSize" value="${c3p0.initialPoolSize}"/>

        <property name="minPoolSize" value="${c3p0.minPoolSize}"/>

        <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>

        <property name="maxIdleTime" value="${c3p0.maxIdleTime}"/>

        <property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}"/>

        <property name="maxStatements" value="${c3p0.maxStatements}"/>

        <property name="numHelperThreads" value="${c3p0.numHelperThreads}"/>

        <property name="preferredTestQuery" value="${c3p0.preferredTestQuery}"/>

        <property name="testConnectionOnCheckout" value="${c3p0.testConnectionOnCheckout}"/>

    </bean>

    

    <!-- 配置SqlSessionFactoryBean -->

    <bean id="oracleSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

        <property name="dataSource" ref="dataSourceOracle"/>

        <property name="configLocation" value="classpath:mybatis.xml"/>

        <!-- mapper和resultmap配置路径 --> 

        <property name="mapperLocations">

            <list>

                <!-- 表示在com.hoo目录下的任意包下的resultmap包目录中,以-resultmap.xml或-mapper.xml结尾所有文件 --> 

                <value>classpath:com/hoo/framework/mybatis/mybatis-common.xml</value>

                <value>classpath:com/hoo/**/resultmap/*-resultmap.xml</value>

                <value>classpath:com/hoo/**/mapper/*-mapper.xml</value>

                <value>classpath:com/hoo/**/mapper/**/*-mapper.xml</value>

            </list>

        </property>

    </bean>

    

    <!-- 配置SqlSessionFactoryBean -->

    <bean id="mysqlSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

        <property name="dataSource" ref="dataSourceMySQL"/>

        <property name="configLocation" value="classpath:mybatis.xml"/>

        <!-- mapper和resultmap配置路径 -->  

        <property name="mapperLocations">

            <list>

                <!-- 表示在com.hoo目录下的任意包下的resultmap包目录中,以-resultmap.xml或-mapper.xml结尾所有文件 (oracle和mysql扫描的配置和路径不一样,如果是公共的都扫描 这里要区分下,不然就报错 找不到对应的表、视图)--> 

                <value>classpath:com/hoo/framework/mybatis/mybatis-common.xml</value>

                <value>classpath:com/hoo/**/resultmap/*-mysql-resultmap.xml</value>

                <value>classpath:com/hoo/**/mapper/*-mysql-mapper.xml</value>

                <value>classpath:com/hoo/**/mapper/**/*-mysql-mapper.xml</value>

                <value>classpath:com/hoo/**/mapper/**/multiple-datasource-mapper.xml</value>

            </list>

        </property>

    </bean>    

    

    <!-- 配置自定义的SqlSessionTemplate模板,注入相关配置 -->

    <bean id="sqlSessionTemplate" class="com.hoo.framework.mybatis.support.CustomSqlSessionTemplate">

        <constructor-arg ref="oracleSqlSessionFactory" />

        <property name="targetSqlSessionFactorys">

            <map>     

                <entry value-ref="oracleSqlSessionFactory" key="oracle"/>

                <entry value-ref="mysqlSqlSessionFactory" key="mysql"/>

            </map> 

        </property>

    </bean>

    

    <!-- 通过扫描的模式,扫描目录在com/hoo/任意目录下的mapper目录下,所有的mapper都需要继承SqlMapper接口的接口 -->

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

        <property name="basePackage" value="com.hoo.**.mapper"/>

        <!-- 注意注入sqlSessionTemplate -->

        <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>

        <property name="markerInterface" value="com.hoo.framework.mybatis.SqlMapper"/>

    </bean> 

    

</beans>

上面的配置关键是在MapperScannerConfigurer中注入sqlSessionTemplate,这个要注意。当我们配置了多个SqlSessionFactoryBean的时候,就需要为MapperScannerConfigurer指定一个sqlSessionFactoryBeanName或是sqlSessionTemplateBeanName。一般情况下注入了sqlSessionTemplateBeanName对象,那sqlSessionFactory也就有值了。如果单独的注入了sqlSessionFactory那么程序会创建一个sqlSessionTemplate对象。我们可以看看代码SqlSessionFactoryDaoSupport对象的代码。如果你不喜欢使用扫描的方式,也可以注入sqlSessionTemplate或继承sqlSessionTemplate完成数据库操作。

public abstract class SqlSessionDaoSupport extends DaoSupport {

 

  private SqlSession sqlSession;

 

  private boolean externalSqlSession;

 

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {

    if (!this.externalSqlSession) {

      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);

    }

  }

 

  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {

    this.sqlSession = sqlSessionTemplate;

    this.externalSqlSession = true;

  }

......

这段代码很明显,如果注入了sqlSessionTemplate上面的注入也就不会执行了。如果没有注入sqlSessionTemplate,那么会自动new一个sqlSessionTemplate对象。

3、编写相关测试接口和实现的mapper.xml

package com.hoo.server.datasource.mapper;

 

import java.util.List;

import java.util.Map;

import com.hoo.framework.mybatis.SqlMapper;

 

/**

 * <b>function:</b> MyBatis 多数据源 测试查询接口

 * @author hoojo

 * @createDate 2013-10-10 下午04:18:08

 * @file MultipleDataSourceMapper.java

 * @package com.hoo.server.datasource.mapper

 * @project SHMB

 * @blog http://blog.csdn.net/IBM_hoojo

 * @email hoojo_@126.com

 * @version 1.0

 */

public interface MultipleDataSourceMapper extends SqlMapper {

 

    public List<Map<String, Object>> execute4MySQL() throws Exception;

    

    public List<Map<String, Object>> execute4Oracle() throws Exception;

}

multiple-datasource-mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.hoo.server.datasource.mapper.MultipleDataSourceMapper">

    

    <select id="execute4Oracle" resultType="map">

        <![CDATA[

            SELECT

                *

            FROM

                deviceInfo_tab t where rownum < 10

        ]]>

    </select>

    

    <select id="execute4MySQL" resultType="map">

        <![CDATA[

            SELECT

                *

            FROM

                city limit 2

        ]]>

    </select>

</mapper>

上面分别查询oracle和mysql两个数据库中的table

 

4、测试代码

@Autowired

@Qualifier("multipleDataSourceMapper")

private MultipleDataSourceMapper mapper;

 

@Test

public void testMapper() {

    CustomerContextHolder.setContextType(CustomerContextHolder.SESSION_FACTORY_MYSQL);

    try {

        trace(mapper.execute4MySQL());

    } catch (Exception e1) {

        e1.printStackTrace();

    }

    CustomerContextHolder.setContextType(CustomerContextHolder.SESSION_FACTORY_ORACLE);

    try {

        trace(mapper.execute4Oracle());

    } catch (Exception e) {

        e.printStackTrace();

    }

}

运行后发现能够顺利查询出数据。

 

如果你是重写SqlSessionDaoSupport,那么方法如下

package com.hoo.framework.mybatis.support;

 

import java.util.Map;

import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.SqlSessionUtils;

import org.mybatis.spring.support.SqlSessionDaoSupport;

import org.springframework.beans.BeansException;

import org.springframework.context.ApplicationContext;

import org.springframework.context.ApplicationContextAware;

 

/**

 * <b>function:</b> MyBatis 动态SqlSessionFactory

 * @author hoojo

 * @createDate 2013-10-14 下午02:32:19

 * @file DynamicSqlSessionDaoSupport.java

 * @package com.hoo.framework.mybatis.support

 * @project SHMB

 * @blog http://blog.csdn.net/IBM_hoojo

 * @email hoojo_@126.com

 * @version 1.0

 */

public class DynamicSqlSessionDaoSupport extends SqlSessionDaoSupport implements ApplicationContextAware {

 

    private ApplicationContext applicationContext;

    

    private Map<Object, SqlSessionFactory> targetSqlSessionFactorys;

    private SqlSessionFactory defaultTargetSqlSessionFactory;

    private SqlSession sqlSession;

 

    @Override

    public final SqlSession getSqlSession() {

        SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(CustomerContextHolder.getContextType());

        if (targetSqlSessionFactory != null) {

            setSqlSessionFactory(targetSqlSessionFactory);

        } else if (defaultTargetSqlSessionFactory != null) {

            setSqlSessionFactory(defaultTargetSqlSessionFactory);

            targetSqlSessionFactory = defaultTargetSqlSessionFactory;

        } else {

            targetSqlSessionFactory = (SqlSessionFactory) applicationContext.getBean(CustomerContextHolder.getContextType());

            setSqlSessionFactory(targetSqlSessionFactory);

        }

        this.sqlSession = SqlSessionUtils.getSqlSession(targetSqlSessionFactory);

        return this.sqlSession;

    }

 

    @Override

    protected void checkDaoConfig() {

        //Assert.notNull(getSqlSession(), "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");

    }

 

    public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) {

        this.targetSqlSessionFactorys = targetSqlSessionFactorys;

    }

 

    public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {

        this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;

    }

 

    @Override

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        this.applicationContext = applicationContext;

    }

}

主要重写getSqlSession方法,上面获取SqlSessionFactory的方法。

重写好了后就可以配置这个对象,配置代码如下

//每一个DAO由继承SqlSessionDaoSupport全部改为DynamicSqlSessionDaoSupport  

public class UserMapperDaoImpl extends DynamicSqlSessionDaoSupport implements UserDao {  

  

    public int addUser(User user) {            

        return this.getSqlSession().insert("com.hoo.user.dao.UserDao.addUser", user);  

    }    

} 

 

在上面的配置文件中加入配置

<bean id="baseDao" class="com.hoo.framework.mybatis.support.DynamicSqlSessionDaoSupport" abstract="true" lazy-init="true">

    <property name="targetSqlSessionFactorys">

        <map>     

            <entry value-ref="oracleSqlSessionFactory" key="oracle"/>

            <entry value-ref="mysqlSqlSessionFactory" key="mysql"/>

        </map> 

    </property>

    <property name="defaultTargetSqlSessionFactory" ref="oracleSqlSessionFactory"/>

   </bean>

 

<bean id="userMapperDao" class="com.hoo.user.dao.impl.UserMapperDaoImpl" parent="baseDao"/>

就这样也可以利用DynamicSqlSessionDaoSupport来完成动态切换sqlSessionFactory对象,只需用在注入userMapperDao调用方法的时候设置下CustomerContextHolder的contextType即可。

 

三、总结

为了实现这个功能看了mybatis-spring-1.2.0.jar这个包的部分源代码,代码内容不是很多。所以看了下主要的代码,下面做些简单的介绍。

MapperScannerConfigurer这个类就是我们要扫描的Mapper接口的类,也就是basePackage中继承markerInterface配置的接口。可以看看ClassPathBeanDefinitionScanner、ClassPathMapperScanner中的doScan这个方法。它会扫描basePackage这个包下所有接口,在ClassPathScanningCandidateComponentProvider中有这个方法findCandidateComponents,它会找到所有的BeanDefinition。

最重要的一点是ClassPathMapperScanner中的doScan这个方法它会给这些接口创建一个MapperFactoryBean。并且会检查sqlSessionFactory和sqlSessionTemplate对象的注入情况。

所以我们配置扫描的方式也就相当于我们在配置文件中给每一个Mapper配置一个MapperFactoryBean一样。而这个MapperFactoryBean又继承SqlSessionDaoSupport。所以当初我想重写MapperScannerConfigurer中的postProcessBeanDefinitionRegistry方法,然后重写方法中的ClassPathMapperScanner中的doScan方法,将definition.setBeanClass(MapperFactoryBean.class);改成自己定义的MapperFactoryBean。最后以失败告终,因为这里是Spring装载扫描对象的时候都已经为这些对象创建好了代理、设置好了mapperInterface和注入需要的类。所以在调用相关操作数据库的API方法的时候,设置对应的SqlSessionFactory也是无效的。

辗转反侧我看到了SqlSessionTemplate这个类,它的功能相当于SqlSessionDaoSupport的实现类MapperFactoryBean。最为关键的是SqlSessionTemplate有一个拦截器SqlSessionInterceptor,它复制所有SqlSession的创建、提交、关闭,而且是在每个方法之前。这点在上面也提到过了!所以我们只需要在SqlSessionInterceptor方法中获取SqlSessionFactory的时候,在这之前调用下CustomerContextHolder.setContextType方法即可完成数据库的SqlSessionFactory的切换。而在MapperScannerConfigurer提供了注入SqlSessionFactory和sqlSessionTemplate的方法,如果注入了SqlSessionFactory系统将会new一个sqlSessionTemplate,而注入了sqlSessionTemplate就不会创建其他对象(见下面代码)。所以我们配置一个sqlSessionTemplate并注入到MapperScannerConfigurer中,程序将会使用这个sqlSessionTemplate。本文最后的实现方式就是这样完成的。

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {

  if (!this.externalSqlSession) {

    this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);

  }

}

 

public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {

  this.sqlSession = sqlSessionTemplate;

  this.externalSqlSession = true;

}

Spring3 整合MyBatis3 配置多数据源 动态选择SqlSessionFactory的更多相关文章

  1. Spring3 整合MyBatis3 配置多数据源 动态选择SqlSessionFactory(转)

    1. Spring整合MyBatis切换SqlSessionFactory有两种方法,第一. 继承SqlSessionDaoSupport,重写获取SqlSessionFactory的方法.第二.继承 ...

  2. Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法

    一.开篇 这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能.所以在出来数据库方言的时候基 ...

  3. Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源方法

    一.开篇 这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能.所以在出来数据库方言的时候基 ...

  4. Spring Boot整合Druid配置多数据源

    Druid是阿里开发的数据库连接池,功能强大,号称Java语言中最好的数据库连接池.本文主要介绍Srping Boot下用Druid配置多个数据源,demo环境为:Spring Boot 2.1.4. ...

  5. Spring Boot数据访问之多数据源配置及数据源动态切换

    如果一个数据库数据量过大,考虑到分库分表和读写分离需要动态的切换到相应的数据库进行相关操作,这样就会有多个数据源.对于一个数据源的配置在Spring Boot数据访问之数据源自动配置 - 池塘里洗澡的 ...

  6. Spring3 整合Hibernate3.5 动态切换SessionFactory (切换数据库方言)

    一.缘由 上一篇文章Spring3.3 整合 Hibernate3.MyBatis3.2 配置多数据源/动态切换数据源 方法介绍到了怎么样在Sping.MyBatis.Hibernate整合的应用中动 ...

  7. SpringBoot整合MyBatisPlus配置动态数据源

    目录 SpringBoot整合MyBatisPlus配置动态数据源 SpringBoot整合MyBatisPlus配置动态数据源 推文:2018开源中国最受欢迎的中国软件MyBatis-Plus My ...

  8. Spring3.2+mybatis3.2+Struts2.3整合

    1.Spring3.2不能用于JDK1.8,只能用于JDK1.7.JDK1.8用spring4.0. 2.导入的jar包 3.目录结构: 4.配置Spring 配置数据库信息: <?xml ve ...

  9. 一文读懂Spring动态配置多数据源---源码详细分析

    Spring动态多数据源源码分析及解读 一.为什么要研究Spring动态多数据源 ​ 期初,最开始的原因是:想将答题服务中发送主观题答题数据给批改中间件这块抽象出来, 但这块主要使用的是mq消息的方式 ...

随机推荐

  1. IOS线程的一些总结

    主线程的作用 (在主线程中才能设置) 显示/刷新UI界面 处理UI事件(比如点击事件.滚动事件.拖拽事件): 主线程的使用注意 别将比较耗时的操作放到主线程中. 耗时操作会卡住主线程.影响体验. [N ...

  2. [python] 线程池

    特别感谢simomo 什么是线程池? 诸如web服务器.数据库服务器.文件服务器和邮件服务器等许多服务器应用都面向处理来自某些远程来源的大量短小的任务.构建服务器应用程序的一个过于简单的模型是:每当一 ...

  3. boa移植

    1.交叉编译 2.复制文件 配置文件boa.conf 移动到/etc/boa/ 目录下 可执行文件boa移动到/usr/sbin/目录下 3.修改配置文件 4.将Linux系统上/etc/mime.t ...

  4. 【Mysql】phpMyAdmin安装与配置

    phpMyAdmin概念 phpMyAdmin 是一个以PHP为基础,以Web-Base方式架构在网站主机上的MySQL的数据库管理工具,让管理者可用Web接口管理MySQL数据库.借由此Web接口可 ...

  5. as的一些常见问题

    assets文件的存放目录在”src/main/”目录下,和java.res文件夹平级: aidl文件需要单独在”src/main/”目录下新建一个文件夹,然后创建对应的包名,将aidl文件放在包名对 ...

  6. Request Tracker 4.0.13 发布

    Request Tracker 4.0.13 修复了几个重要的安全问题. Request Tracker,企业级的问题跟踪系统

  7. Linux更改主机名的最简单方法

    之前写过一篇关于CentOS更改主机名的随笔,搞得很复杂,详见修改阿里云CentOS Linux服务器的主机名. 今天在askubuntu上发现一个很简单的方法(How do I change the ...

  8. 使用ConditionalScope进行高效的SharePoint CSOM编程

    在上一篇文章中讲述了 ExceptionHandlingScope的使用后,本章主要讲述ConditionalScope的用法. ConditionalScope在设计思路和解决问题上同Excepti ...

  9. Ubuntu 安装配置MySQL,并使用VS的Server Explorer UI界面远程管理MySQL

    为安装配置方便,使用root账号登入Ubuntu. step1: 键入下面命令安装MySQL. 过程十分简单.安装过程中只需根据提示输入root账号的密码即可. step2:安装完成后检查MySQL是 ...

  10. chosen PersistenceUnitInfo does not specify a provider class name

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userServiceI ...