• 从零开发分布式数据库中间件 二、构建MyBatis的读写分离数据库中间件


      在上一节  从零开发分布式数据库中间件 一、读写分离的数据库中间件 中,我们讲了如何通过ThreadLocal来指定每次访问的数据源,并通过jdbc的连接方式来切换数据源,那么这一节我们使用我们常用的数据库持久层框架MyBatis来实现数据库读写分离。

    一、数据源代理:

    此类与上一节相似,即可以指定当前线程访问的数据源。

    1. package com.happyheng.datasource;  
    2.   
    3. /** 
    4.  * 数据源代理设置 
    5.  * Created by happyheng on 17/1/15. 
    6.  */  
    7. public class DataSourceProxy {  
    8.   
    9.     private static ThreadLocal<DataSourceEnum> threadLocal = new ThreadLocal<>();  
    10.   
    11.     public enum DataSourceEnum {  
    12.         MASTER,  
    13.         SLAVE  
    14.     }  
    15.   
    16.     /** 
    17.      * 为当前线程设置数据源 
    18.      */  
    19.     public static void setDataSource(DataSourceEnum sourceEnum) {  
    20.         threadLocal.set(sourceEnum);  
    21.     }  
    22.   
    23.     public static DataSourceEnum getDataSource() {  
    24.         return threadLocal.get();  
    25.     }  
    26.   
    27. }  
    package com.happyheng.datasource;
    
    /**
     * 数据源代理设置
     * Created by happyheng on 17/1/15.
     */
    public class DataSourceProxy {
    
        private static ThreadLocal<DataSourceEnum> threadLocal = new ThreadLocal<>();
    
        public enum DataSourceEnum {
            MASTER,
            SLAVE
        }
    
        /**
         * 为当前线程设置数据源
         */
        public static void setDataSource(DataSourceEnum sourceEnum) {
            threadLocal.set(sourceEnum);
        }
    
        public static DataSourceEnum getDataSource() {
            return threadLocal.get();
        }
    
    }
    

    二、数据源Map:

      首先我们需要将我们的读写数据源都写入到配置文件中,并设置到继承了AbstractRoutingDataSource抽象类的子类中,接下来我们会讲解AbstractRoutingDataSource的作用:

    1. <bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource"  
    2.       destroy-method="close">  
    3.     <property name="driverClassName" value="${master.driver}" />  
    4.     <property name="url" value="${master.dburl}" />  
    5.     <property name="username" value="${master.user}" />  
    6.     <property name="password" value="${master.password}" />  
    7. </bean>  
    8.   
    9. <bean id="slaveDataSource1" class="org.apache.commons.dbcp.BasicDataSource"  
    10.       destroy-method="close">  
    11.     <property name="driverClassName" value="${slave1.driver}" />  
    12.     <property name="url" value="${slave1.dburl}" />  
    13.     <property name="username" value="${slave1.user}" />  
    14.     <property name="password" value="${slave1.password}" />  
    15. </bean>  
    16.   
    17. <bean id="slaveDataSource2" class="org.apache.commons.dbcp.BasicDataSource"  
    18.       destroy-method="close">  
    19.     <property name="driverClassName" value="${slave2.driver}" />  
    20.     <property name="url" value="${slave2.dburl}" />  
    21.     <property name="username" value="${slave2.user}" />  
    22.     <property name="password" value="${slave2.password}" />  
    23. </bean>  
    24.   
    25. <bean id="dataSource" class="com.happyheng.datasource.OptionalDataSource" >  
    26.     <!-- 通过key-value的形式来关联数据源 -->  
    27.     <property name="targetDataSources">  
    28.         <map>  
    29.             <entry key="masterDataSource" value-ref="masterDataSource" />  
    30.             <entry key="slaveDataSource1" value-ref="slaveDataSource1" />  
    31.             <entry key="slaveDataSource2" value-ref="slaveDataSource2" />  
    32.         </map>  
    33.     </property>  
    34.     <property name="defaultTargetDataSource" ref="masterDataSource" />  
    35. </bean>  
    36.   
    37. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
    38.     <property name="dataSource" ref="dataSource" />  
    39.     <property name="mapperLocations" value="classpath*:mybatis/**/*Mapper.xml"/>  
    40. </bean>  
        <bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource"
              destroy-method="close">
            <property name="driverClassName" value="${master.driver}" />
            <property name="url" value="${master.dburl}" />
            <property name="username" value="${master.user}" />
            <property name="password" value="${master.password}" />
        </bean>
    
        <bean id="slaveDataSource1" class="org.apache.commons.dbcp.BasicDataSource"
              destroy-method="close">
            <property name="driverClassName" value="${slave1.driver}" />
            <property name="url" value="${slave1.dburl}" />
            <property name="username" value="${slave1.user}" />
            <property name="password" value="${slave1.password}" />
        </bean>
    
        <bean id="slaveDataSource2" class="org.apache.commons.dbcp.BasicDataSource"
              destroy-method="close">
            <property name="driverClassName" value="${slave2.driver}" />
            <property name="url" value="${slave2.dburl}" />
            <property name="username" value="${slave2.user}" />
            <property name="password" value="${slave2.password}" />
        </bean>
    
        <bean id="dataSource" class="com.happyheng.datasource.OptionalDataSource" >
            <!-- 通过key-value的形式来关联数据源 -->
            <property name="targetDataSources">
                <map>
                    <entry key="masterDataSource" value-ref="masterDataSource" />
                    <entry key="slaveDataSource1" value-ref="slaveDataSource1" />
                    <entry key="slaveDataSource2" value-ref="slaveDataSource2" />
                </map>
            </property>
            <property name="defaultTargetDataSource" ref="masterDataSource" />
        </bean>
    
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="mapperLocations" value="classpath*:mybatis/**/*Mapper.xml"/>
        </bean>

    二、AbstractRoutingDataSource数据源路由类:

      在MyBatis中,需从SqlSessionFactory中获取dao文件,而SqlSessionFactory即需要数据源,因为我们需要根据不同的情况来选定数据源,所以不能写死一个数据源,而是应该将数据源写入到AbstractRoutingDataSource中的map中,AbstractRoutingDataSource即能够根据不同的情况指定访问的数据源。

      还有需要注意的是,AbstractRoutingDataSource是一个抽象类,需要实现其determineCurrentLookupKey方法,来指定每次访问数据库的数据源。

    下为继承了AbstractRoutingDataSource的OptionalDataSource类:

    1. package com.happyheng.datasource;  
    2.   
    3. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;  
    4.   
    5. /** 
    6.  * Created by happyheng on 17/1/10. 
    7.  */  
    8. public class OptionalDataSource extends AbstractRoutingDataSource {  
    9.   
    10.     // 数据源  
    11.     private String masterDataSource = "masterDataSource";  
    12.     private String[] slaveDataSource = {"slaveDataSource1", "slaveDataSource2"};  
    13.   
    14.     @Override  
    15.     protected Object determineCurrentLookupKey() {  
    16.   
    17.   
    18.         DataSourceProxy.DataSourceEnum dataSourceEnum = DataSourceProxy.getDataSource();  
    19.   
    20.         if (dataSourceEnum == DataSourceProxy.DataSourceEnum.SLAVE) {  
    21.   
    22.             double random = Math.random();  
    23.             int randomIndex = (int)(random * slaveDataSource.length);  
    24.   
    25.             System.out.println("访问的是从数据库" + (randomIndex + 1));  
    26.             return slaveDataSource[randomIndex];  
    27.         } else {  
    28.   
    29.             System.out.println("访问的是主数据库");  
    30.             return masterDataSource;  
    31.         }  
    32.     }  
    33. }  
    package com.happyheng.datasource;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * Created by happyheng on 17/1/10.
     */
    public class OptionalDataSource extends AbstractRoutingDataSource {
    
        // 数据源
        private String masterDataSource = "masterDataSource";
        private String[] slaveDataSource = {"slaveDataSource1", "slaveDataSource2"};
    
        @Override
        protected Object determineCurrentLookupKey() {
    
    
            DataSourceProxy.DataSourceEnum dataSourceEnum = DataSourceProxy.getDataSource();
    
            if (dataSourceEnum == DataSourceProxy.DataSourceEnum.SLAVE) {
    
                double random = Math.random();
                int randomIndex = (int)(random * slaveDataSource.length);
    
                System.out.println("访问的是从数据库" + (randomIndex + 1));
                return slaveDataSource[randomIndex];
            } else {
    
                System.out.println("访问的是主数据库");
                return masterDataSource;
            }
        }
    }
    

      首先,我们将一主两从的数据源都写入到OptionalDataSource的map中,而每次MyBatis访问数据库时,都会调用此类的determineCurrentLookupKey()来获取数据源map中的key,从而得到对应的数据源。

      可以看出,当我们发现是访问从数据库时,使用随机法来获取从数据库数据源,当发现是访问主数据库时,直接访问主数据库数据源。

    4、此项目已在github上开源,可以完整实现MyBatis的数据库读写分离,地址为:github。如果觉得不错,那么就star一下来鼓励我吧。 

  • 相关阅读:
    Host文件修改后无效的解决办法
    spring-framework-reference阅读笔记(一)
    利用instsrv和srvany来手动安装服务
    JSP
    DHTML (一)
    Java 多线程 (概述)
    Java IO (三)
    Java IO(二)
    Java 递归
    Java IO File (一)
  • 原文地址:https://www.cnblogs.com/firstdream/p/7851364.html
Copyright © 2020-2023  润新知