Spring+Mybatis多数据源的一种实现方式,支持事务

作者:依天照海

来自:http://www.cnblogs.com/ieinstein/

参考:http://www.cnblogs.com/lzrabbit/p/3750803.html

最近一个项目用到了多个数据库,所以需要实现动态切换数据源来查询数据,http://www.cnblogs.com/lzrabbit/p/3750803.html这篇文章让我受益匪浅,提供了一种自动切换数据源的思路,但这种方式不支持事务,所以我进一步改进了这个方案,下面直入正题

多数据源配置:

#============================================================================
# DataBaseOne
#============================================================================
jdbc.one.driver=com.mysql.jdbc.Driver
jdbc.one.url=jdbc:mysql://127.0.0.1:3306/DataBaseOne?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
jdbc.one.username=root
jdbc.one.password=root
=============================================================================

#
============================================================================
# DataBaseTwo
#============================================================================
jdbc.two.driver=com.mysql.jdbc.Driver
jdbc.two.url=jdbc:mysql://127.0.0.1:3306/DataBaseTwo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
jdbc.two.username=root
jdbc.two.password=root
=============================================================================

#
============================================================================
# DataBaseThree
#============================================================================
jdbc.three.driver=com.mysql.jdbc.Driver
jdbc.three.url=jdbc:mysql://127.0.0.1:3306/DataBaseThree?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
jdbc.three.username=root
jdbc.mysql.password=root
=============================================================================


#
============================================================================
# 通用配置
#============================================================================
jdbc.initialSize=5
jdbc.minIdle=5
jdbc.maxIdle=20
jdbc.maxActive=100
jdbc.maxWait=100000
jdbc.defaultAutoCommit=false
jdbc.removeAbandoned=true
jdbc.removeAbandonedTimeout=600
jdbc.testWhileIdle=true
jdbc.timeBetweenEvictionRunsMillis=60000
jdbc.numTestsPerEvictionRun=20
jdbc.minEvictableIdleTimeMillis=300000

Spring中使用多数据源:


xml version="1.0" encoding="UTF-8"?>
<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:aop="http://www.springframework.org/schema/aop"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-3.0.xsd
     http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
>


   

        
   <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:jdbc.properties"/>
   bean>

   
   <bean id="dataSourceOne" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.one.driver}"/>
       <property name="url" value="${jdbc.one.url}"/>
       <property name="username" value="${jdbc.one.username}"/>
       <property name="password" value="${jdbc.one.password}"/>
       <property name="initialSize" value="${jdbc.initialSize}"/>
       <property name="minIdle" value="${jdbc.minIdle}"/>
       <property name="maxIdle" value="${jdbc.maxIdle}"/>
       <property name="maxActive" value="${jdbc.maxActive}"/>
       <property name="maxWait" value="${jdbc.maxWait}"/>
       <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/>
       <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>
       <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
        <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
       <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
        <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/>
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
    bean>
   
   <bean id="dataSourceTwo" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.two.driver}"/>
       <property name="url" value="${jdbc.two.url}"/>
       <property name="username" value="${jdbc.two.username}"/>
       <property name="password" value="${jdbc.two.password}"/>
       <property name="initialSize" value="${jdbc.initialSize}"/>
       <property name="minIdle" value="${jdbc.minIdle}"/>
       <property name="maxIdle" value="${jdbc.maxIdle}"/>
       <property name="maxActive" value="${jdbc.maxActive}"/>
       <property name="maxWait" value="${jdbc.maxWait}"/>
       <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/>
       <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>
       <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
        <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
       <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
        <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/>
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
    bean>
   
   <bean id="dataSourceThree" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.three.driver}"/>
       <property name="url" value="${jdbc.three.url}"/>
       <property name="username" value="${jdbc.three.username}"/>
       <property name="password" value="${jdbc.three.password}"/>
       <property name="initialSize" value="${jdbc.initialSize}"/>
       <property name="minIdle" value="${jdbc.minIdle}"/>
       <property name="maxIdle" value="${jdbc.maxIdle}"/>
       <property name="maxActive" value="${jdbc.maxActive}"/>
       <property name="maxWait" value="${jdbc.maxWait}"/>
       <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/>
       <property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>
       <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/>
        <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
       <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
        <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/>
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
    bean>
   
   <bean id="multipleDataSource" class="com.cnblogs.datasource.MultipleDataSource">
       
       <property name="defaultTargetDataSource" ref="dataSourceOne"/>
       <property name="targetDataSources">
           <map>
               
                <entry key="dataSourceOneKey" value-ref="dataSourceOne"/>
               <entry key="dataSourceTwoKey" value-ref="dataSourceTwo"/>
               <entry key="dataSourceThreeKey" value-ref="dataSourceThree"/>
           map>
       property>
   bean>
   
   <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
       <property name="dataSource" ref="multipleDataSource"/>
   bean>

   
   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
       <property name="basePackage" value="com.cnblogs.mapper"/>
   bean>

   
   <context:component-scan base-package="com.cnblogs.**"/>
   
   
   <aop:config>
         
        <aop:pointcut id="myPointcut"
           expression="execution(* com.cnblogs.service.*.*(..))" />

         
       <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut" />  
   aop:config>
beans>



MultipleDataSource.java实现:

package com.cnblogs.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/** 多数据源java实现 */
public class MultipleDataSource extends AbstractRoutingDataSource {
   private static final ThreadLocal dataSourceKey = new InheritableThreadLocal();

   public static void setDataSourceKey(String dataSource) {
       dataSourceKey.set(dataSource);
   }

   @Override
   protected Object determineCurrentLookupKey() {
       return dataSourceKey.get();
   }
}

下面详解使用SpringAOP来实现数据源切换,并支持事务控制

上面我们配置的AOP是针对service层的,所以在调用service层的任何方法时都会经过AOP,因此我们就在AOP中先于service调用时把数据源切换,看代码

新建类MultipleDataSourceAspectAdvice,把MultipleDataSourceAspectAdvice.java放在spring能自动注入的包中,比如controller包,或者自己把这个类所在的包加入到component-scan配置中去,总之要要让spring能自动加载

package com.cnblogs.controller;

import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.cnblogs.datasource.MultipleDataSource;
import com.cnblogs.service.DataBaseOne;
import com.cnblogs.service.DataBaseTwo;
import com.cnblogs.service.DataBaseThree;

/**
* 数据库链接自动切换AOP处理
* Order优先级设置到最高,因为在所有service方法调用前都必须把数据源确定
* Order数值越小优先级越高
*/

@Component
@Aspect
@Order(1)
public class MultipleDataSourceAspectAdvice {
   private static final Logger LOGGER = Logger.getLogger(MultipleDataSourceAspectAdvice.class);

   public MultipleDataSourceAspectAdvice() {
       LOGGER.info("MultipleDataSourceAspectAdvice 加载成功");
   }

   /**
    * 定义切面
    */

   @Pointcut("execution(* com.cnblogs.service.*.*(..))")
   public void pointCut() {
   }

   // dataSourceOneKey
   // dataSourceTwoKey
   // dataSourceThreeKey
   @Around("pointCut()")
   public Object doAround(ProceedingJoinPoint jp) throws Throwable {
       if (jp.getTarget() instanceof DataBaseOne) {
           LOGGER.debug("使用数据库链接:dataSourceOneKey");
           MultipleDataSource.setDataSourceKey("dataSourceOneKey");
       } else if (jp.getTarget() instanceof DataBaseTwo) {
           LOGGER.debug("使用数据库链接:dataSourceTwoKey");
           MultipleDataSource.setDataSourceKey("dataSourceTwoKey");
       } else if (jp.getTarget() instanceof DataBaseThree) {
           LOGGER.debug("使用数据库链接:dataSourceThreeKey");
           MultipleDataSource.setDataSourceKey("dataSourceThreeKey");
       } else {
           // 默认是dataSourceOneKey
           LOGGER.debug("使用数据库链接:dataSourceOneKey");
           MultipleDataSource.setDataSourceKey("dataSourceOneKey");
       }
       return jp.proceed();
   }
}

到这里我们所以的spring多数据源配置已经完毕,那如何在执行service方法时让service切换到正确的数据库呢?上面的类中定义了有3个类DataBaseOne,DataBaseTwo,DataBaseThree,这3个类其实只是一个interface,没有任何实现方法,我们让具体业务的service都继承至这3个类以区分不同的service对应不同的数据源,因为业务的service我是知道他用的哪个数据源的,比如FooService继承至DataBaseOne,则在使用FooService任何方法时AOP就会先把数据源切换到dataSourceOneKey,以此就达到了自动切换数据源的目的,并且支持事务,下面看代码:

package com.cnblogs.service;

/**
* DataBaseOne数据库占位类
* 详情参见 MultipleDataSourceAspectAdvice 类
*/

public interface DataBaseOne {

}


至此多数据源就配置完毕,可以安心写业务逻辑了

你可能感兴趣的:(java)