zoukankan      html  css  js  c++  java
  • spring读写分离(配置多数据源)[marked]

    我们今天的主角是AbstractRoutingDataSource,在Spring2.0.1发布之后,引入了AbstractRoutingDataSource,使用该类可以实现普遍意义上的多数据源管理功能。

     1、扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。)

        从AbstractRoutingDataSource的源码中:

    public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

    我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:

    public Connection getConnection() throws SQLException {  
        return determineTargetDataSource().getConnection();  
    }  
        
    public Connection getConnection(String username, String password) throws SQLException {  
         return determineTargetDataSource().getConnection(username, password);  
    }

    获取连接的方法中,重点是determineTargetDataSource()方法,看源码: 

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

    上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。

        看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

    public class DynamicDataSource extends AbstractRoutingDataSource{   
        
        @Override   
        protected Object determineCurrentLookupKey() {   
            return DBContextHolder.getDBType();   
        }   
    }

    DataSourceHolder这个类则是我们自己封装的对数据源进行操作的类:

    public class DataSourceHolder {    
        //线程本地环境
        private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();    
        //设置数据源
        public static void setDataSource(String customerType) {
            dataSources.set(customerType);
        }    //获取数据源
        public static String getDataSource() {        
           return (String) dataSources.get();
        }    //清除数据源
        public static void clearDataSource() {
            dataSources.remove();
        }
      
    }

    setDataSource如何使用呢?我们可以在程序里面自己写:

    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");   
    BaseDAO dao = (BaseDAO) context.getBean("sqlBaseDAO", BaseDAOImpl.class);   
        
    try {   
        DataSourceHolder.setDataSource("data1");   
        System.err.println(dao.select("select count(*) sum from TEST t ").get(0).get("SUM"));   
        DataSourceHolder.setDataSource("data2");   
        System.err.println(dao.select("select count(*) sum from TEST t ").get(0).get("SUM"));   
            
    } catch (Exception e) {   
        e.printStackTrace();   
    } finally{
        DataSourceHolder.clearDataSource();
    }

    也可以用更优雅的方式aop,把配置的数据源类型都设置成为注解标签,在service层中需要切换数据源的方法上,写上注解标签,调用相应方法切换数据源:

    @DataSource(name=DataSource.slave1)
    public List getProducts(){
    }
    import java.lang.annotation.*;
      
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documentedpublic @interface DataSource {
        String name() default DataSource.master; 
        public static String master = "dataSource1"; 
        public static String slave1 = "dataSource2"; 
        public static String slave2 = "dataSource3";
    }

    有时候我们可能要实现的功能是读写分离,要求select走一个库,update,delete走一个库,我们有了规则就不想每个方法都去写注解了

    我们先把spring配置文件搞上:

    <bean id = "dataSource1" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">   
            <property name="url" value="${db1.url}"/>
            <property name = "user" value = "${db1.user}"/>
            <property name = "password" value = "${db1.pwd}"/>
            <property name="autoReconnect" value="true"/>
            <property name="useUnicode"  value="true"/>
            <property name="characterEncoding" value="UTF-8"/>
        </bean>
      
        <bean id = "dataSource2" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
            <property name="url" value="${db2.url}"/>
            <property name = "user" value = "${db2.user}"/>
            <property name = "password" value = "${db2.pwd}"/>
            <property name="autoReconnect" value="true"/>
            <property name="useUnicode"  value="true"/>
            <property name="characterEncoding" value="UTF-8"/>
        </bean>
      
        <bean id = "dataSource3" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
            <property name="url" value="${db3.url}"/>
            <property name = "user" value = "${db3.user}"/>
            <property name = "password" value = "${db3.pwd}"/>
            <property name="autoReconnect" value="true"/>
            <property name="useUnicode"  value="true"/>
            <property name="characterEncoding" value="UTF-8"/>
        </bean>
        <!-- 配置多数据源映射关系 -->
        <bean id="dataSource" class="com.datasource.test.util.database.DynamicDataSource">
            <property name="targetDataSources">
                <map key-type="java.lang.String">
                    <entry key="dataSource1" value-ref="dataSource1"></entry>
                    <entry key="dataSource2" value-ref="dataSource2"></entry>
                    <entry key="dataSource3" value-ref="dataSource3"></entry>
                </map>
            </property>
        <!-- 默认目标数据源为你主库数据源 -->
            <property name="defaultTargetDataSource" ref="dataSource1"/>
        </bean>
        <!-- JdbcTemplate使用动态数据源的配置 -->     
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">     
            <property name="dataSource">     
                <ref bean="dynamicDataSource" />     
            </property>     
        </bean>

    接着我们还是接着上面的aop在一定规则下的配置

    <aop:config expose-proxy="true">
    <aop:pointcut id="txPointcut" expression="execution(* com.jwdstef..service.impl..*.*(..))" />
    <aop:aspect ref="readWriteInterceptor" order="1">
    <aop:around pointcut-ref="txPointcut" method="readOrWriteDB"/>
    </aop:aspect>
    </aop:config>
     
    <bean id="readWriteInterceptor" class="com.test.ReadWriteInterceptor">
       <property name="readMethodList">
         <list>
           <value>query*</value>
           <value>use*</value>
           <value>get*</value>
           <value>count*</value>
           <value>find*</value>
           <value>list*</value>
           <value>search*</value>
        </list>
      </property>
     
    <property name="writeMethodList">
        <list>
            <value>save*</value>
            <value>add*</value>
            <value>create*</value>
            <value>insert*</value>
            <value>update*</value>
            <value>merge*</value>
            <value>del*</value>
            <value>remove*</value>
            <value>put*</value>
            <value>write*</value>
       </list>
    </property>
    </bean>

    配置的切面类是ReadWriteInterceptor。这样当Mapper接口的方法被调用时,会先调用这个切面类的readOrWriteDB方法。在这里需要注意<aop:aspect>中的order="1" 配置,主要是为了解决切面于切面之间的优先级问题,因为整个系统中不太可能只有一个切面类。

    public class ReadWriteInterceptor {
       private static final String DB_SERVICE = "dbService";
       private List<String> readMethodList = new ArrayList<String>();
       private List<String> writeMethodList = new ArrayList<String>();
       public Object readOrWriteDB(ProceedingJoinPoint pjp) throws Throwable {
            String methodName = pjp.getSignature().getName();
            if (isChooseReadDB(methodName)) {
                //选择slave数据源
                DataSourceHolder.setDataSource("data1");
            } else if (isChooseWriteDB(methodName)) {
               //选择master数据源
               DataSourceHolder.setDataSource("data2");
            } else {
              //选择master数据源
              DataSourceHolder.setDataSource("data1");
            }
           return pjp.proceed();
    }
     
     private boolean isChooseWriteDB(String methodName) {
         for (String mappedName : this.writeMethodList) {
             if (isMatch(methodName, mappedName)) {
                 return true;
             }
         }
        return false;
    }
     
     private boolean isChooseReadDB(String methodName) {
        for (String mappedName : this.readMethodList) {
           if (isMatch(methodName, mappedName)) {
               return true;
           }
        }
        return false;
    }
     
     private boolean isMatch(String methodName, String mappedName) {
        return PatternMatchUtils.simpleMatch(mappedName, methodName);
    }
     
     public List<String> getReadMethodList() {
        return readMethodList;
     }
     
     public void setReadMethodList(List<String> readMethodList) {
       this.readMethodList = readMethodList;
    }
     
     public List<String> getWriteMethodList() {
        return writeMethodList;
     }
     
     public void setWriteMethodList(List<String> writeMethodList) {
        this.writeMethodList = writeMethodList;
    }

    一般来说,是一主多从,即一个master库,多个slave库的,所以还得解决多个slave库之间负载均衡、故障转移以及失败重连接等问题。

    1、负载均衡问题,slave不多,系统并发读不高的话,直接使用随机数访问也是可以的。就是根据slave的台数,然后产生随机数,随机的访问slave。

    2、故障转移,如果发现connection获取不到了,则把它从slave列表中移除,等其回复后,再加入到slave列表中

    3、失败重连,第一次连接失败后,可以多尝试几次,如尝试10次。

  • 相关阅读:
    oracle中Blob和Clob类型的区别
    为什么要分库分表
    Enable file editing in Visual Studio's debug mode
    SQL Server Dead Lock Log
    Debug .NET Framework Source
    SQL Server text field里面有换行符的时候copy到excel数据会散乱
    诊断和修复Web测试记录器(Web Test Recorder)问题
    Can't load Microsoft.ReportViewer.ProcessingObjectModel.dll
    'telnet' is not recognized as an internal or external command
    Linq to XML
  • 原文地址:https://www.cnblogs.com/mignet/p/5279576.html
Copyright © 2011-2022 走看看