Menu
mybatis多数据源切换实现多用户SAAS软件
mybatis多数据源切换实现多用户SAAS软件,使用多数据源的场景应该是很多的,如操作同一台服务器上不同的数据库,或者多地机器上的相同或不相同数据库。
1.mybatis切换数据源
[html] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"  
  4.     xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task"  
  5.     xmlns:aop="http://www.springframework.org/schema/aop"  
  6.     xsi:schemaLocation="http://www.springframework.org/schema/beans    
  7.     http://www.springframework.org/schema/beans/spring-beans-4.1.xsd     
  8.     http://www.springframework.org/schema/context     
  9.     http://www.springframework.org/schema/context/spring-context-4.1.xsd    
  10.     http://www.springframework.org/schema/tx  
  11.     http://www.springframework.org/schema/tx/spring-tx-4.1.xsd  
  12.     http://www.springframework.org/schema/task   
  13.     http://www.springframework.org/schema/task/spring-task-4.1.xsd       
  14.     http://www.springframework.org/schema/aop   
  15.     http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"  
  16.     default-lazy-init="false">  
  17.   
  18.     <!-- 定时器开关 开始 -->  
  19.     <task:annotation-driven />  
  20.     <tx:annotation-driven />  
  21.     <!-- 标注类型 的事务配置 如果使用注解事务。就放开 <tx:annotation-driven /> -->  
  22.     <!-- 统一异常处理方式 -->  
  23.     <!-- <bean id="exceptionHandler" class="com.framework.exception.MyExceptionHandler"/> -->  
  24.     <!-- 初始化数据 -->  
  25.     <!-- <bean id="SpringIocUtils" class="com.framework.util.SpringIocUtils"   
  26.         lazy-init="default" init-method="setBean"/> -->  
  27.   
  28.     <bean  
  29.         class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
  30.         <property name="locations">  
  31.             <list>  
  32.                 <value>classpath:jdbc.properties</value>  
  33.             </list>  
  34.         </property>  
  35.         <property name="ignoreUnresolvablePlaceholders" value="true" />  
  36.     </bean>  
  37.     <bean id="dynamicDataSource" class="com.framework.plugin.test.DynamicDataSource">  
  38.         <property name="targetDataSources">  
  39.             <map key-type="java.lang.String">  
  40.                 <entry value-ref="dataSource" key="dataSource"></entry>  
  41.                 <entry value-ref="dataSource2" key="dataSource2"></entry>  
  42.             </map>  
  43.         </property>  
  44.         <property name="defaultTargetDataSource" ref="dataSource">  
  45.         </property>  
  46.     </bean>  
  47.   
  48.     <bean id="dataSource" class="junit.test.JDBCTest">  
  49.         <property name="url" value="${jdbc.url}" />  
  50.         <property name="username" value="${jdbc.username}" />  
  51.         <property name="password" value="${jdbc.password}" />  
  52.         <property name="driverClassName" value="${jdbc.driverClass}" />  
  53.     </bean>  
  54.     <bean id="dataSource2" class="junit.test.JDBCTest">     
  55.         <property name="url" value="jdbc:mysql://127.0.0.1/db_shiro" />  
  56.         <property name="username" value="root" />  
  57.         <property name="password" value="123456" />  
  58.         <property name="driverClassName" value="${jdbc.driverClass}" />  
  59.     </bean>  
  60.     <!-- lpl 自定义注册 -->  
  61.     <bean id="springfactory" class="com.framework.util.SpringFactory"></bean>  
  62.   
  63.     <bean id="pagePlugin" class="com.framework.plugin.PagePlugin">  
  64.         <property name="properties">  
  65.             <props>  
  66.                 <prop key="dialect">mysql</prop>  
  67.                 <prop key="pageSqlId">.*query.*</prop>  
  68.             </props>  
  69.         </property>  
  70.     </bean>  
  71.     <bean id="sqlSessionFactoryBean" class="com.framework.plugin.test.MySqlFatoryBean">  
  72.         <property name="dataSource" ref="dynamicDataSource" />  
  73.         <!-- 自动匹配Mapper映射文件 -->  
  74.         <property name="mapperLocations" value="classpath:mappings/*-mapper.xml" />  
  75.         <!-- <property name="typeAliasesPackage" value="com.framework.entity"/> -->  
  76.         <property name="plugins">  
  77.             <array>  
  78.                 <ref bean="pagePlugin" />  
  79.             </array>  
  80.         </property>  
  81.     </bean>  
  82.     <!-- 通过扫描的模式,扫描目录在com.framework.mapper目录下,所有的mapper都继承SqlMapper接口的接口, 这样一个bean就可以了 -->  
  83.     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
  84.         <property name="basePackage" value="com.framework.mapper" />  
  85.     </bean>  
  86.     <!-- 事务配置 -->  
  87.     <bean id="transactionManager"  
  88.         class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  89.         <property name="dataSource" ref="dynamicDataSource" />  
  90.     </bean>  
  91.     <!-- <aop:config> <aop:pointcut expression="execution(public * com.framework.controller.*(..))"   
  92.         id="pointcut" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"   
  93.         /> </aop:config> <tx:advice id="txAdvice" transaction-manager="transactionManager">   
  94.         <tx:attributes> <tx:method name="query*" propagation="REQUIRED" read-only="true"   
  95.         /> <tx:method name="find*" propagation="REQUIRED" read-only="true" /> <tx:method   
  96.         name="save*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED"   
  97.         /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="modify*"   
  98.         propagation="REQUIRED" /> <tx:method name="logicDelById" propagation="REQUIRED"   
  99.         /> </tx:attributes> </tx:advice> -->  
  100.     <!-- <aop:aspectj-autoproxy proxy-target-class="true"/> <bean id="log4jHandlerAOP"   
  101.         class="com.framework.logAop.LogAopAction"></bean> <aop:config proxy-target-class="true">   
  102.         <aop:aspect id="logAspect" ref="log4jHandlerAOP"> <aop:pointcut id="logPointCut"   
  103.         expression="execution(* org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(..))"   
  104.         /> <aop:around method="logAll" pointcut-ref="logPointCut" /> </aop:aspect>   
  105.         </aop:config> -->  
  106.     <!-- 使用Spring组件扫描的方式来实现自动注入bean -->  
  107.     <!-- <context:component-scan base-package="com.framework.task" /> -->  
  108.     <!-- 隐式地向 Spring 容器注册 -->  
  109.     <!-- <context:annotation-config /> -->  
  110. </beans>  
[java] view plain copy
  1. package junit.test;  
  2.   
  3.   
  4. import java.sql.SQLException;  
  5. import java.util.Properties;  
  6.   
  7. import org.springframework.jdbc.datasource.DriverManagerDataSource;  
  8.   
  9. import com.framework.util.JDBCConnectionUtil;  
  10.   
  11. public class JDBCTest extends DriverManagerDataSource{  
  12.       
  13.       
  14.     public void changeJDBC(String url,String username,String password) {  
  15.         setUrl(url);  
  16.         setUsername(username);  
  17.         setPassword(password);  
  18.     }  
  19.       
  20.     public void changeJDBC(Properties properties) throws Exception {  
  21.         setConnectionProperties(properties);  
  22.         setUrl(properties.getProperty("jdbc.url"));  
  23.         setUsername(properties.getProperty("jdbc.username"));  
  24.         setPassword(properties.getProperty("jdbc.password"));  
  25.     }  
  26. }  
通过继承DriverManagerDataSource类实现数据源动态加载
 
[java] view plain copy
  1. package com.framework.plugin.test;  
  2.   
  3. import org.mybatis.spring.SqlSessionFactoryBean;  
  4. import org.mybatis.spring.SqlSessionTemplate;  
  5.   
  6. public class MySqlFatoryBean extends SqlSessionFactoryBean {  
  7.       
  8.     public void test() {  
  9.     }  
  10. }  
ThreadLocal<String> contextHolder创建多线程实现对变量的保存
[java] view plain copy
  1. package com.framework.plugin.test;  
  2.   
  3. public class ContextHolder {    
  4.     public static final String DATA_SOURCE_A = "dataSource";    
  5.     public static final String DATA_SOURCE_B = "dataSource2";    
  6.     private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();    
  7.     public static void setCustomerType(String customerType) {    
  8.         contextHolder.set(customerType);    
  9.         System.out.println(" contextHolder.set(customerType);  ==="+customerType);  
  10.     }    
  11.     public static String getCustomerType() {    
  12.         return contextHolder.get();    
  13.     }    
  14.     public static void clearCustomerType() {    
  15.         contextHolder.remove();    
  16.     }    
  17. }    
只要每次调用setCustomerType方法(集合中存在)就可以实现数据源的动态切换
如何动态增长数据源呢,map,就是突破口。
利用spring,得到对象
[java] view plain copy
  1. JDBCTest jdbcTest = new JDBCTest();  
  2.         Properties properties = MapToProperties.map2properties(datacenterFormMap);  
  3.         String username = datacenterFormMap.getStr("username");  
  4.         jdbcTest.changeJDBC(properties);  
  5.         DynamicDataSource dataSource = (DynamicDataSource) SpringFactory.getObject("dynamicDataSource");  
  6.         HashMap<String, JDBCTest> hashMap = (HashMap<String, JDBCTest>) ReflectHelper.getValueByFieldName(dataSource,  
  7.                 "resolvedDataSources");  
  8.         hashMap.put(username, jdbcTest);  
  9.         System.out.println(ReflectHelper.getFieldByFieldName(dataSource, "targetDataSources"));  
  10.         ContextHolder.setCustomerType(username);  
 
3.如何满足saas多租户模式的需求。
通过拦截器interceptor。SaasInterceptor
不断拦截用户的请求,不断更换。
[java] view plain copy
  1. /**  
  2.      * Retrieve the current target DataSource. Determines the  
  3.      * {@link #determineCurrentLookupKey() current lookup key}, performs  
  4.      * a lookup in the {@link #setTargetDataSources targetDataSources} map,  
  5.      * falls back to the specified  
  6.      * {@link #setDefaultTargetDataSource default target DataSource} if necessary.  
  7.      * @see #determineCurrentLookupKey()  
  8.      */    
  9.     protected DataSource determineTargetDataSource() {    
  10.         Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");    
  11.         Object lookupKey = determineCurrentLookupKey();    
  12.         DataSource dataSource = this.resolvedDataSources.get(lookupKey);    
  13.         if (dataSource == null && (this.lenientFallback || lookupKey == null)) {    
  14.             dataSource = this.resolvedDefaultDataSource;    
  15.         }    
  16.         if (dataSource == null) {    
  17.             throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");    
  18.         }    
  19.         return dataSource;    
  20.     }  
 
真正实现自动切换的地方。。
 
现在,记录相关的dynamicdatasource与spring 的相关特性,
每一次,进入spring,都会自动执行一边这个方法。
 
还发现,就算又是明明切换,但是我的框架,切实数据源切换失败的,着实有点蛋疼。。
既然这样,我就改写底层框架,使他变成,适应多数据源的spring。
 
[java] view plain copy
  1. private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {  
  2.    Statement stmt;  
  3.    Connection connection = getConnection(statementLog);  
  4.    stmt = handler.prepare(connection);  
  5.    handler.parameterize(stmt);  
  6.    return stmt;  
  7.  }  
都是在这里执行connection的
 
进去看看,怎么得到的
[java] view plain copy
  1. protected Connection getConnection(Log statementLog) throws SQLException {  
  2.    Connection connection = transaction.getConnection();  
  3.    if (statementLog.isDebugEnabled()) {  
  4.      return ConnectionLogger.newInstance(connection, statementLog, queryStack);  
  5.    } else {  
  6.      return connection;  
  7.    }  
  8.  }  
[java] view plain copy
  1. /* 
  2.    * Creates a logging version of a connection 
  3.    * 
  4.    * @param conn - the original connection 
  5.    * @return - the connection with logging 
  6.    */  
  7.   public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {  
  8.     InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);  
  9.     ClassLoader cl = Connection.class.getClassLoader();  
  10.     return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);  
  11.   }  
 
connection,跟statementlog没有关系,只跟transaction有关系。
[java] view plain copy
  1. protected BaseExecutor(Configuration configuration, Transaction transaction) {  
  2.     this.transaction = transaction;  
  3.     this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();  
  4.     this.localCache = new PerpetualCache("LocalCache");  
  5.     this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");  
  6.     this.closed = false;  
  7.     this.configuration = configuration;  
  8.     this.wrapper = this;  
  9.   }