<h1>
    <span class="link_title"><a href="/happyheng/article/details/54628737">
    从零开发分布式数据库中间件 二、构建MyBatis的读写分离数据库中间件        
       
    </a>
    </span>

     
</h1>

  在上一节  从零开发分布式数据库中间件 一、读写分离的数据库中间件 中,我们讲了如何通过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>  
&lt;bean 
      destroy-method="close"&gt;
    &lt;property name="driverClassName" value="${slave1.driver}" /&gt;
    &lt;property name="url" value="${slave1.dburl}" /&gt;
    &lt;property name="username" value="${slave1.user}" /&gt;
    &lt;property name="password" value="${slave1.password}" /&gt;
&lt;/bean&gt;

&lt;bean 
      destroy-method="close"&gt;
    &lt;property name="driverClassName" value="${slave2.driver}" /&gt;
    &lt;property name="url" value="${slave2.dburl}" /&gt;
    &lt;property name="username" value="${slave2.user}" /&gt;
    &lt;property name="password" value="${slave2.password}" /&gt;
&lt;/bean&gt;

&lt;bean  &gt;
    &lt;!-- 通过key-value的形式来关联数据源 --&gt;
    &lt;property name="targetDataSources"&gt;
        &lt;map&gt;
            &lt;entry key="masterDataSource" value-ref="masterDataSource" /&gt;
            &lt;entry key="slaveDataSource1" value-ref="slaveDataSource1" /&gt;
            &lt;entry key="slaveDataSource2" value-ref="slaveDataSource2" /&gt;
        &lt;/map&gt;
    &lt;/property&gt;
    &lt;property name="defaultTargetDataSource" ref="masterDataSource" /&gt;
&lt;/bean&gt;

&lt;bean &gt;
    &lt;property name="dataSource" ref="dataSource" /&gt;
    &lt;property name="mapperLocations" value="classpath*:mybatis/**/*Mapper.xml"/&gt;
&lt;/bean&gt;</pre><br>

二、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一下来鼓励我吧。 



相关文章:

  • 2021-10-10
  • 2022-12-23
  • 2021-12-18
  • 2021-12-18
  • 2022-03-15
  • 2021-11-09
  • 2022-12-23
猜你喜欢
  • 2022-02-08
  • 2021-08-15
  • 2021-06-21
  • 2021-06-12
  • 2022-02-09
  • 2021-06-29
相关资源
相似解决方案