spring+mybatis 多数据源切换,动态数据源增长,saas多租户模式方案

最近公司做一个saas模式的项目。在多数据源问题上卡了几天。有两个方案,这是1.0。

你们不知道我多辛苦,翻遍了中国网站,翻遍了外国网站。

前者不全面,不严谨,后者看不懂。




我们先从,多数据源切换开始。

[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.       
  22.       
  23.       
  24.       
  25.       
  26.     <bean id="springfactory" class="com.framework.util.SpringFactory">bean>  
  27.   
  28.     <bean id="pagePlugin" class="com.framework.plugin.PagePlugin">  
  29.         <property name="properties">  
  30.             <props>  
  31.                 <prop key="dialect">mysqlprop>  
  32.                 <prop key="pageSqlId">.*query.*prop>  
  33.             props>  
  34.         property>  
  35.     bean>  
  36.     <bean id="sqlSessionFactoryBean" class="com.framework.plugin.test.MySqlFatoryBean">  
  37.         <property name="dataSource" ref="dynamicDataSource" />  
  38.           
  39.         <property name="mapperLocations" value="classpath:mappings/*-mapper.xml" />  
  40.           
  41.         <property name="plugins">  
  42.             <array>  
  43.                 <ref bean="pagePlugin" />  
  44.             array>  
  45.         property>  
  46.     bean>  
  47.       
  48.     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
  49.         <property name="basePackage" value="com.framework.mapper" />  
  50.     bean>  
  51.       
  52.     <bean id="transactionManager"  
  53.         class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  54.         <property name="dataSource" ref="dynamicDataSource" />  
  55.     bean>  
  56.       
  57.       
  58.       
  59.       
  60. 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. }  
上面这个类,我只是贪图方便然后就继承了,来使用。


[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 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 contextHolder = new ThreadLocal();    
  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 hashMap = (HashMap) 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


不断拦截用户的请求,不断更换。

这个方案很低级,不高效。

sqlsession,也还是单例的,没有saas的思想。就是一个伪saas。


/** 
     * 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;  
    }

真正实现自动切换的地方。。


现在,记录相关的dynamicdatasource与spring 的相关特性,

每一次,进入spring,都会自动执行一边这个方法。


还发现,就算又是明明切换,但是我的框架,切实数据源切换失败的,着实有点蛋疼。。

既然这样,我就改写底层框架,使他变成,适应多数据源的spring。

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
  }
都是在这里执行connection的

进去看看,怎么得到的

 protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

我的乖乖,越来越复杂,,

/*
   * Creates a logging version of a connection
   *
   * @param conn - the original connection
   * @return - the connection with logging
   */
  public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
    InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
    ClassLoader cl = Connection.class.getClassLoader();
    return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
  }

最后总结,connection,跟statementlog没有半毛钱关系,只跟transaction有关系。

protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

翻了整个类,transaction,只是初始的时候,完成了初始化。。

transaction又是一个接口来的,必然有实现类,不翻了,猜想应该就是xml里配置的transactionManager,有关。

其实就是线索断了。。

那么实现就是,要么硬来,可以实现,我测试了。代码,很简单。

就是改变原有的connection,变成自定义。

这样的解决办法,相当暴力,那么耦合性就会很强。


其实还有下一篇的,等我BY了,我就写出来了。

 QQ交流群315309006 

你可能感兴趣的:(我的文档)