Spring+Mybatis多数据源的实现

来源:互联网 发布:js框架有哪些 编辑:程序博客网 时间:2024/05/22 00:17
第一种方法:创建两个会话工厂 
配置如下 
Java代码  
  1. <beans>  
  2.     <!-- 数据源1 -->  
  3.     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">    
  4.          <!-- 数据库基本信息配置 -->  
  5.          <property name="url" value="${url}" />    
  6.          <property name="username" value="${username}" />    
  7.          <property name="password" value="${password}" />    
  8.          <property name="driverClassName" value="${driverClassName}" />    
  9.          <property name="filters" value="${filters}" />    
  10.         <!-- 最大并发连接数 -->  
  11.          <property name="maxActive" value="${maxActive}" />  
  12.          <!-- 初始化连接数量 -->  
  13.          <property name="initialSize" value="${initialSize}" />  
  14.          <!-- 配置获取连接等待超时的时间 -->  
  15.          <property name="maxWait" value="${maxWait}" />  
  16.          <!-- 最小空闲连接数 -->  
  17.          <property name="minIdle" value="${minIdle}" />    
  18.     </bean>    
  19.     <!-- 数据源2 -->  
  20.     <bean id="syncDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">    
  21.          <property name="url" value="${sync.url}" />    
  22.          <property name="username" value="${sync.username}" />    
  23.          <property name="password" value="${sync.password}" />    
  24.          <property name="driverClassName" value="${sync.driverClassName}" />    
  25.          <property name="filters" value="${filters}" />    
  26.          <property name="maxActive" value="${maxActive}" />  
  27.          <property name="initialSize" value="${initialSize}" />>  
  28.          <property name="maxWait" value="${maxWait}" />  
  29.          <property name="minIdle" value="${minIdle}" />    
  30.     </bean>    
  31.     <!-- 第一个sqlSessionFactory -->  
  32.     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
  33.         <property name="dataSource" ref="dataSource" />  
  34.         <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>  
  35.         <!-- mapper扫描 -->  
  36.         <property name="mapperLocations" value="classpath:mybatis/*/*.xml"></property>  
  37.     </bean>  
  38.       
  39.     <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">  
  40.         <constructor-arg ref="sqlSessionFactory" />  
  41.     </bean>  
  42.     <!-- 第二个sqlSessionFactory -->  
  43.     <bean id="syncSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
  44.         <property name="dataSource" ref="syncDataSource" />  
  45.         <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>  
  46.         <property name="mapperLocations" value="classpath:mybatis/sync/*.xml"></property>  
  47.     </bean>         
  48.     <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">     
  49.             <property name="dataSource" ref="dataSource"></property>  
  50.     </bean>  
  51.     <bean id="syncSqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">  
  52.         <constructor-arg ref="syncSqlSessionFactory" />  
  53.     </bean>  
  54.     <!-- <aop:aspectj-autoproxy proxy-target-class="true" />  -->  
  55. </beans>  

这种方法,在程序中只需要切换sqlSessionTemplate即可,但是这样第二数据源无事务, 
容易抛出TransactionSynchronizationManager.unbindResourceIfPossible异常,这也是其缺点 
是否可以再添加一个事务管理器?下载

Java代码  
  1. <bean name="syncTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">     
  2.             <property name="dataSource" ref="syncDataSource"></property>  
  3. </bean>  

你可以试试,我测试没问题。 
jdbc.properties配置文件 
Java代码  
  1. url=jdbc\:mysql\://localhost\:3306/test?useUnicode\=true&characterEncoding\=utf8&characterSetResults\=utf8   
  2. driverClassName=com.mysql.jdbc.Driver  
  3. username=donald  
  4. password=123456  
  5.   
  6. #sync datasource  
  7. sync.url=jdbc\:mysql\://192.168.32.128\:3306/test?useUnicode\=true&characterEncoding\=utf8&characterSetResults\=utf8   
  8. sync.driverClassName=com.mysql.jdbc.Driver  
  9. sync.username=donald  
  10. sync.password=123456  


第二种方法:扩展数据源路由 下载
查看AbstractRoutingDataSource 
//抽象数据源路由 
Java代码  
  1. public abstract class AbstractRoutingDataSource extends AbstractDataSource  
  2.     implements InitializingBean  
  3. {  
  4.   
  5. public void afterPropertiesSet()  
  6.     {  
  7.         if(targetDataSources == null)  
  8.             throw new IllegalArgumentException("Property 'targetDataSources' is required");  
  9.         resolvedDataSources = new HashMap(targetDataSources.size());  
  10.         Object lookupKey;  
  11.         DataSource dataSource;  
  12.     //将配置的多数据源添加到resolvedDataSources中  
  13.         for(Iterator iterator = targetDataSources.entrySet().iterator(); iterator.hasNext(); resolvedDataSources.put(lookupKey, dataSource))  
  14.         {  
  15.             java.util.Map.Entry entry = (java.util.Map.Entry)iterator.next();  
  16.             lookupKey = resolveSpecifiedLookupKey(entry.getKey());  
  17.             dataSource = resolveSpecifiedDataSource(entry.getValue());  
  18.         }  
  19.   
  20.         if(defaultTargetDataSource != null)  
  21.         //默认数据源  
  22.             resolvedDefaultDataSource = resolveSpecifiedDataSource(defaultTargetDataSource);  
  23.     }  
  24.     //数据源key  
  25.     protected Object resolveSpecifiedLookupKey(Object lookupKey)  
  26.     {  
  27.         return lookupKey;  
  28.     }  
  29.     //获取数据源根据dataSource  
  30.     protected DataSource resolveSpecifiedDataSource(Object dataSource)  
  31.         throws IllegalArgumentException  
  32.     {  
  33.         if(dataSource instanceof DataSource)  
  34.             return (DataSource)dataSource;  
  35.         if(dataSource instanceof String)  
  36.        //从bean容器中获取对应的数据源,(DataSource)beanFactory.getBean(dataSourceName, javax/sql/DataSource);  
  37.        // in BeanFactoryDataSourceLookup.getDataSource(String dataSourceName)  
  38.             return dataSourceLookup.getDataSource((String)dataSource);  
  39.         else  
  40.             throw new IllegalArgumentException((new StringBuilder()).append("Illegal data source value - only [javax.sql.DataSource] and String supported: ").append(dataSource).toString());  
  41.     }  
  42.     //获取连接  
  43.      public Connection getConnection()  
  44.         throws SQLException  
  45.     {  
  46.        //再看determineTargetDataSource  
  47.         return determineTargetDataSource().getConnection();  
  48.     }  
  49.     protected DataSource determineTargetDataSource()  
  50.     {  
  51.         //获取当前数据源名,这里是关键  
  52.         Object lookupKey = determineCurrentLookupKey();  
  53.     //获取当前数据源  
  54.         DataSource dataSource = (DataSource)resolvedDataSources.get(lookupKey);  
  55.         if(dataSource == null && (lenientFallback || lookupKey == null))  
  56.         //如果dataSource为空,则dataSource为默认的数据源resolvedDataSources  
  57.             dataSource = resolvedDefaultDataSource;  
  58.         if(dataSource == null)  
  59.             throw new IllegalStateException((new StringBuilder()).append("Cannot determine target DataSource for lookup key [").append(lookupKey).append("]").toString());  
  60.         else  
  61.             return dataSource;  
  62.     }  
  63.     //determineCurrentLookupKey方法,为抽象方法,待子类扩展,这是不是给了我们一种思路  
  64.     protected abstract Object determineCurrentLookupKey();  
  65.     private Map targetDataSources;//Map<String,DataSource>,key为数据源名,value为DataSource  
  66.     private Object defaultTargetDataSource;  
  67.     private boolean lenientFallback;  
  68.     private DataSourceLookup dataSourceLookup;  
  69.     private Map resolvedDataSources;//Map<String,DataSource>,key为数据源名,value为DataSource  
  70.     private DataSource resolvedDefaultDataSource;  
  71. }  

从分析AbstractRoutingDataSource获取数据源得出,想要实现多数据源,只需要扩展AbstractRoutingDataSource,并实现determineCurrentLookupKey方法即可,并在determineCurrentLookupKey方法中切换数据源名即可。 
下面实验: 下载
//数据源路由 
Java代码  
  1. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;  
  2. /** 
  3.  * 动态切换数据源 
  4.  * @author donald 
  5.  * 
  6.  */  
  7. public class MultipleRoutingDataSource extends AbstractRoutingDataSource{  
  8.     @Override  
  9.     protected Object determineCurrentLookupKey() {  
  10.         return DataSourceContextHolder.getDataSourceType();  
  11.     }  

} 
//这里我们创建一个数据源上下文句柄,以便切换数据源 下载
Java代码  
  1. /** 
  2.  * 数据源上下文 
  3.  * @author donald 
  4.  * 
  5.  */  
  6. public  class DataSourceContextHolder {  
  7.     public final static String DATA_SOURCE_LOCAL = "dataSource";  
  8.     public final static String DATA_SOURCE_SYNC = "syncDataSource";  
  9.     //对数据源名,线程隔离  
  10.     private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();  
  11.       
  12.     public static void setDataSourceType(String dataSource) {    
  13.         contextHolder.set(dataSource);    
  14.     }        
  15.     public static String getDataSourceType() {    
  16.         return contextHolder.get();    
  17.     }     
  18.     public static void clearDataSourceType() {    
  19.         contextHolder.remove();    
  20.     }    
  21. }  

//配置如下 下载
Java代码  
  1. <beans>  
  2.         <!-- 数据源1 -->  
  3.     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">    
  4.          <!-- 数据库基本信息配置 -->  
  5.          <property name="url" value="${url}" />    
  6.          <property name="username" value="${username}" />    
  7.          <property name="password" value="${password}" />    
  8.          <property name="driverClassName" value="${driverClassName}" />    
  9.          <property name="filters" value="${filters}" />    
  10.         <!-- 最大并发连接数 -->  
  11.          <property name="maxActive" value="${maxActive}" />  
  12.          <!-- 初始化连接数量 -->  
  13.          <property name="initialSize" value="${initialSize}" />  
  14.          <!-- 配置获取连接等待超时的时间 -->  
  15.          <property name="maxWait" value="${maxWait}" />  
  16.          <!-- 最小空闲连接数 -->  
  17.          <property name="minIdle" value="${minIdle}" />    
  18.     </bean>    
  19.     <!-- 数据源2 -->  
  20.     <bean id="syncDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">    
  21.          <property name="url" value="${sync.url}" />    
  22.          <property name="username" value="${sync.username}" />    
  23.          <property name="password" value="${sync.password}" />    
  24.          <property name="driverClassName" value="${sync.driverClassName}" />    
  25.          <property name="filters" value="${filters}" />    
  26.          <property name="maxActive" value="${maxActive}" />  
  27.          <property name="initialSize" value="${initialSize}" />>  
  28.          <property name="maxWait" value="${maxWait}" />  
  29.          <property name="minIdle" value="${minIdle}" />    
  30.     </bean>    
  31.     <!-- 数据源路由 -->  
  32.     <bean id="multipleDataSource" class="com.dataSource.MultipleRoutingDataSource">  
  33.             <!-- 默认数据源 -->  
  34.         <property name="defaultTargetDataSource" ref="dataSource"/>  
  35.                  <!-- 目标数据源 -->  
  36.         <property name="targetDataSources">  
  37.             <map>       
  38.             <!-- 注意这里的value是和上面的DataSource的id对应,key要和  
  39.                            下面的DataSourceContextHolder中的常量对应 -->  
  40.             <entry value-ref="dataSource" key="dataSource"/>  
  41.             <entry value-ref="syncDataSource" key="syncDataSource"/>  
  42.             </map>     
  43.         </property>  
  44.     </bean>  
  45.     <!-- 配置mybatis -->  
  46.     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
  47.              <!-- 这里为multipleDataSource,可以统一管理事务 -->  
  48.         <property name="dataSource" ref="multipleDataSource" />  
  49.         <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>  
  50.         <!-- mapper扫描 -->  
  51.         <property name="mapperLocations" value="classpath:mybatis/*/*.xml"></property>  
  52.         </bean>  
  53.     <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">  
  54.         <constructor-arg ref="sqlSessionFactory" />  
  55.     </bean>  
  56.     <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">     
  57.             <property name="dataSource" ref="multipleDataSource"></property>  
  58.     </bean>  
  59.     <!-- <aop:aspectj-autoproxy proxy-target-class="true" />  -->  
  60. </beans>  

手动测试 下载
Java代码  
  1. @Controller  
  2. @RequestMapping(value="/test")  
  3. public class TestController extends BaseController{  
  4.     private static Logger log = LoggerFactory.getLogger(TestController.class);  
  5.     @Resource(name = "daoSupport")  
  6.     private DaoSupport dao;  
  7.       
  8.     @SuppressWarnings("unchecked")  
  9.     @RequestMapping("/db")  
  10.     public void testDbSource(HttpServletResponse response) throws IOException {  
  11.         log.debug("=======Into testDbSource==============");  
  12.         PageData pd = this.getPageData();  
  13.         try {  
  14.             DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.DATA_SOURCE_LOCAL);  
  15.             List<PageData> lpd = (List<PageData>) dao.findForList("test.list", pd);  
  16.             log.info("=============localDao size:"+lpd.size()+","+DataSourceContextHolder.getDataSourceType());  
  17.             DataSourceContextHolder.clearDataSourceType();  
  18.         } catch (Exception e) {  
  19.             log.error(e.getMessage());  
  20.             e.printStackTrace();  
  21.         }  
  22.         try {  
  23.             DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.DATA_SOURCE_SYNC);  
  24.             List<PageData> lpdTest = (List<PageData>) dao.findForList("test.list", pd);  
  25.             log.info("=============syncDao size:"+lpdTest.size()+","+DataSourceContextHolder.getDataSourceType());  
  26.             DataSourceContextHolder.clearDataSourceType();  
  27.         } catch (Exception e) {  
  28.             log.error(e.getMessage());  
  29.             e.printStackTrace();  
  30.         }  
  31.         response.getWriter().write("test");  
  32.     }  
  33. }  

访问http://localhost:8080/r/test/db.do控制台输出 
2016-09-21 17:57:13 -40921 [com.controller.test.TestController] INFO    - =============localDao size:5,dataSource 
2016-09-21 17:57:13 -40941 [com.controller.test.TestController] INFO    - =============syncDao size:4,syncDataSource 
上面的方式是手动切换数据源,下面我们通过Spring AOP实现动态切换数据源: 
定义注解 下载
Java代码  
  1. @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. @Documented  
  4. public @interface DbSource {  
  5.   
  6.     String value() default "";  
  7.   
  8. }  

定义AOP 
Java代码  
  1. /** 
  2.  * 根据DAO的DbSource的值,动态切换数据源 
  3.  * @author donald 
  4.  * 
  5.  */  
  6. @Aspect  
  7. @Component  
  8. public class DataSourceAspect {  
  9.   
  10.     private static final Logger log = LoggerFactory.getLogger(DataSourceAspect.class);  
  11.         //定义切点  
  12.     @Pointcut("@annotation(com.fh.dataSource.DbSource)")  
  13.     public void dbSourceAspect() {}  
  14.       
  15. /*  @Before("dbSourceAspect()") 
  16.     public void doBefore(JoinPoint joinPoint) { 
  17.         try { 
  18.             System.out.println("Dao-class:" + (joinPoint.getTarget().getClass().getName())); 
  19.             System.out.println("DataSource:" + getDbSourceValue(joinPoint)); 
  20.         } catch (Exception e) { 
  21.             // 记录本地异常日志 
  22.             logger.error("exception", e.getMessage()); 
  23.         } 
  24.     }*/  
  25.         /** 
  26.      * 切换数据源 
  27.      * @param joinPoint 
  28.      */  
  29.     @Around("dbSourceAspect()")  
  30.     public void doAround(ProceedingJoinPoint  joinPoint)  {  
  31.         try {  
  32.             log.info("=============Dao-class:" + (joinPoint.getTarget().getClass().getName()));  
  33.             log.info("=============DataSource:" + getDbSourceValue(joinPoint));  
  34.             joinPoint.proceed();  
  35.             DataSourceContextHolder.clearDataSourceType();  
  36.         }   
  37.         catch(Throwable e){  
  38.             log.error("=============Throwable:", e.getMessage());  
  39.         }  
  40.     }  
  41.  /** 
  42.   * 获取数据源id 
  43.   * @param joinPoint 
  44.   * @return 
  45.   * @throws Exception 
  46.   */  
  47.     @SuppressWarnings({ "rawtypes""unchecked" })  
  48.     public static String getDbSourceValue(JoinPoint joinPoint) throws Exception {  
  49.         //根据连接点获取class  
  50.         String targetName = joinPoint.getTarget().getClass().getName();  
  51.         Class targetClass = Class.forName(targetName);  
  52. //      DbSource dbSource = (DbSource) targetClass.getAnnotation(DbSource.class);  
  53.         //根据连接点获取method  
  54.         String methodName = joinPoint.getSignature().getName();  
  55.         //根据连接点获取args  
  56.         Object[] arguments = joinPoint.getArgs();  
  57.         Method[] methods = targetClass.getMethods();  
  58.         String dbId ="";  
  59.         //获取注解连接点的值  
  60.         for (Method method : methods) {  
  61.             if (method.getName().equals(methodName)) {  
  62.                 Class[] clazzs = method.getParameterTypes();  
  63.                 if (clazzs.length == arguments.length) {  
  64.                 dbId = method.getAnnotation(DbSource.class).value();  
  65.                 if(!StringUtils.isBlank(dbId)){  
  66.                     DataSourceContextHolder.setDataSourceType(dbId);  
  67.                 }  
  68.                 else{  
  69.                     dbId = DataSourceContextHolder.DATA_SOURCE_LOCAL;  
  70.                     DataSourceContextHolder.setDataSourceType(DataSourceContextHolder.DATA_SOURCE_LOCAL);  
  71.                 }  
  72.                 break;  
  73.              }  
  74.           }  
  75.         }  
  76.         return dbId;  
  77.     }  
  78. }  

0 0