spring + mybatis 多数据源动态切换实现

最近在项目中需要用到多数据源,进行数据半夜同步。研究了两天。mark一下。

在正常的项目中,我们经常是一个datasource 对应一个sessionFactory,在DAO层调用sessionFactory访问数据库。如下图所示:

07090506_9Ma1.jpg

但是在某些项目中,需要用到的数据源不止一个。这个时候就需要配置多个数据源。

第一种方法:配置多个多个sessionFactory

这种方法就是在项目中为每一个数据源都配置一个sessionFatory,在DAO层通过调用不同的sessionFactory访问不同的数据库。如下图所示:

07090506_cXyU.jpg

但是这种方法的弊端也比较明显:

1、如果日后datasource 数据源增加或减少,还需要修改对应的代码

2、对sessionFactory 的调用写在dao层中,这样增加了DAO层中代码的维护量。

第二种方法:

采用动态数据源的模式,讲多个数据源交由动态数据源来管理,sessionFactory 调用动态数据源返回的数据源,动态数据源负责数据源的切换。

07090506_PJRw.jpg

部分代码实现:

两个数据源配置:

<!-- 数据源 -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<!-- 基本属性 url、user、password -->
		<property name="url" value="${connection.url}" />
		<property name="username" value="${connection.username}" />
		<property name="password" value="${connection.password}" />
		<property name="driverClassName" value="${connection.driver}"></property>

		<!-- 配置初始化大小、最小、最大 -->
		<property name="initialSize" value="${druid.initialSize}" />
		<property name="minIdle" value="${druid.minIdle}" />
		<property name="maxActive" value="${druid.maxActive}" />

		<!-- 配置获取连接等待超时的时间 -->
		<property name="maxWait" value="${druid.maxWait}" />
		<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
		<property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />

		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />

		<property name="validationQuery" value="${druid.validationQuery}" />
		<property name="testWhileIdle" value="${druid.testWhileIdle}" />
		<property name="testOnBorrow" value="${druid.testOnBorrow}" />
		<property name="testOnReturn" value="${druid.testOnReturn}" />

		<!-- 打开PSCache,并且指定每个连接上PSCache的大小 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。 -->
		<property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" />
		<property name="maxPoolPreparedStatementPerConnectionSize"
			value="${druid.maxPoolPreparedStatementPerConnectionSize}" />

		<!-- 配置监控统计拦截的filters -->
		<property name="filters" value="${druid.filters}" />
	</bean>

	<!-- 同步数据源 -->
	<bean id="syncDataSource" class="com.alibaba.druid.pool.DruidDataSource"
		init-method="init" destroy-method="close">
		<!-- 基本属性 url、user、password -->
		<property name="url" value="${syncConnection.url}" />
		<property name="username" value="${syncConnection.username}" />
		<property name="password" value="${syncConnection.password}" />
		<property name="driverClassName" value="${syncConnection.driver}"></property>

		<!-- 配置初始化大小、最小、最大 -->
		<property name="initialSize" value="${syncDruid.initialSize}" />
		<property name="minIdle" value="${syncDruid.minIdle}" />
		<property name="maxActive" value="${syncDruid.maxActive}" />

		<!-- 配置获取连接等待超时的时间 -->
		<property name="maxWait" value="${syncDruid.maxWait}" />
		<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
		<property name="timeBetweenEvictionRunsMillis" value="${syncDruid.timeBetweenEvictionRunsMillis}" />

		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="${syncDruid.minEvictableIdleTimeMillis}" />

		<property name="validationQuery" value="${syncDruid.validationQuery}" />
		<property name="testWhileIdle" value="${syncDruid.testWhileIdle}" />
		<property name="testOnBorrow" value="${syncDruid.testOnBorrow}" />
		<property name="testOnReturn" value="${syncDruid.testOnReturn}" />

		<!-- 打开PSCache,并且指定每个连接上PSCache的大小 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。 -->
		<property name="poolPreparedStatements" value="${syncDruid.poolPreparedStatements}" />
		<property name="maxPoolPreparedStatementPerConnectionSize"
			value="${syncDruid.maxPoolPreparedStatementPerConnectionSize}" />

		<!-- 配置监控统计拦截的filters -->
		<property name="filters" value="${syncDruid.filters}" />
	</bean>

配置动态数据源:

	<!-- 动态数据源 -->
	<bean id="dynamicDataSource" class="com.XXX.XXXX.core.dataSource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <!--第一个数据源-->
        		<entry key="dataSource" value-ref="dataSource"></entry>
                <!--第二个数据源-->
                <entry key="syncDataSource" value-ref="syncDataSource"></entry>
            </map>
        </property>
    	<!-- 默认目标数据源为你主库数据源 -->
        <property name="defaultTargetDataSource" ref="dataSource"/>
    </bean>

sessionFactory 配置

<bean id="baseSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 数据源配置 -->
		<property name="dataSource" ref="dynamicDataSource" />
		<!-- mybatis参数配置 -->
		<property name="configLocation" value="classpath:datasource/sqlMapConfig.xml"></property>
		<!-- 类型包引用在xml中可以省略包名 -->
		<property name="typeAliasesPackage"
			value="com.XXXX.XXXXX.core.vo,com.XXXX.XXXXX.core.po"></property>
		<!-- 获取mybatis映射的mapper文件 -->
		<property name="mapperLocations">
			<list>
				<value>classpath:datasource/*/*Mapper.xml</value>
			</list>
		</property>
	</bean>

我们看到,在动态数据源配置中我们自定义了一个DynamicDataSource类。这个类就是动态数据源管理类

public class DynamicDataSource extends AbstractRoutingDataSource {

    /* (non Javadoc) 
     * @Title: determineCurrentLookupKey
     * @Description: 获取动态数据源
     * @return 
     * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey() 
     */
    @Override
    protected Object determineCurrentLookupKey() {
        
        return DataSourceContextHolder.getDataSourceType();
    }
}

在这个类中,我们继承了spring中的AbstractRoutingDataSource 的类,实现determineCurrentLookupKey这个方法。我们在spring的源码中看到,在AbstractRoutingDataSource获取数据源的时候,先调用determineCurrentLookupKey方法获取lookupkey,根据获取数据源。

/**
	 * Retrieve the current target DataSource. Determines the
	 * {@link #determineCurrentLookupKey() current lookup key}, performs
	 * a lookup in the {@link #setTargetDataSources targetDataSources} map,
	 * falls back to the specified
	 * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
	 * @see #determineCurrentLookupKey()
	 */
	protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		Object lookupKey = determineCurrentLookupKey();
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
		if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
			dataSource = this.resolvedDefaultDataSource;
		}
		if (dataSource == null) {
			throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
		}
		return dataSource;
	}

	/**
	 * Determine the current lookup key. This will typically be
	 * implemented to check a thread-bound transaction context.
	 * <p>Allows for arbitrary keys. The returned key needs
	 * to match the stored lookup key type, as resolved by the
	 * {@link #resolveSpecifiedLookupKey} method.
	 */
	protected abstract Object determineCurrentLookupKey();

创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识

public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    
    private static final String defaultType = "dataSource";
    
    /** 
     * @Title: setDataSourceType 
     * @Description: 设置数据源类型
     * @param dataSourceType
     * @return: void
     */
    public static void setDataSourceType(String dataSourceType){
        contextHolder.set(dataSourceType);
    }
    
    
    /** 
     * @Title: getDataSourceType 
     * @Description: 获取数据源类型
     * @return
     * @return: String
     */
    public static String getDataSourceType(){
        return contextHolder.get();
    }
    
    /** 
     * @Title: clearDataSourceType 
     * @Description:清除数据源类型
     * @return
     * @return: String
     */
    public static void clearDataSourceType() {  
        contextHolder.remove();  
    }  
    
    
    /** 
     * @Title: setDefaultDataSourceType 
     * @Description: 设置默认数据源类型
     * @return: void
     */
    public static void setDefaultDataSourceType(){
        clearDataSourceType();
        setDataSourceType(defaultType);
    }
}

至此,多数据源动态切换配置完成,我们在junit中测试:

 @Test
    public void test() {
        //设置数据源为syncDataSource
        DataSourceContextHolder.setDataSourceType("syncDataSource");
        List<SyncClass> list = syncService.getList();
        List<TurbineClass> TurbineClassList = new ArrayList<TurbineClass>();
        Date curentDate = new Date();
        try {
            for (SyncTurbineClass syncTurbineClass : list) {
                //业务逻辑
                TurbineClassList.add(turbineClass);
            }
            if (TurbineClassList.size() > 0) {
                //设置数据源为dataSource
                DataSourceContextHolder.setDataSourceType("dataSource");
                syncService.insertByBatchForTurbineClass(TurbineClassList);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

在项目中我们每次切换数据源都需要再代码中手动设置,这样数据源切换代码和业务逻辑代码就混在一起。我们可以将数据源的动态切换抽出为spring的aop,通过注解来实现

我们先定义一个DataSource的注解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface DataSource {
    String value();
}

然后定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中

public class DataSourceAspect  {
    /** 
     * @Title: intercept 
     * @Description: 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
     * @param joinPoint
     * @return: void
     */
    public void intercept(JoinPoint joinPoint){
        Class<?> target = joinPoint.getTarget().getClass();
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        for(Class<?> clazz : target.getInterfaces()){
            resolveDataSource(clazz,methodSignature.getMethod());
        }
        
        resolveDataSource(target,methodSignature.getMethod());
    }
    
    public void resolveDataSource(Class<?> clazz, Method method){
        
        try {
            if(clazz.isAnnotationPresent(DataSource.class)){
                DataSource source = clazz.getAnnotation(DataSource.class);
                DataSourceContextHolder.setDataSourceType(source.value());
            }
            
            Class<?>[] types = method.getParameterTypes();
            Method m = clazz.getDeclaredMethod(method.getName(), types);
            if(null != m && m.isAnnotationPresent(DataSource.class)){
                DataSource source = m.getAnnotation(DataSource.class);
                DataSourceContextHolder.setDataSourceType(source.value());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }

}

在spring的配置文件中,我们定义下这个切面,使其生效:

<!--动态数据源切换 注解配置-->
	<bean id="dataSourceAspect" class="com.XXX.XXXX.core.aspect.DataSourceAspect" />
	<aop:config>
        <aop:aspect ref="dataSourceAspect" order="0">
            <aop:pointcut id="dataSourcePointcut" expression="execution(* com.XXXX.XXXXX.core.service..*.*(..))"/>
            <aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
        </aop:aspect>
    </aop:config>

至此,我们的动态数据源切换注解就配置完成,我们只要在对应的service方法或者类上加上@DataSource注解,即可实现数据源动态切换。

 

参考:

http://www.cnblogs.com/liujiduo/p/5004691.html

http://blog.csdn.net/wangpeng047/article/details/8866239/

转载于:https://my.oschina.net/u/2307799/blog/1329883

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值