基於Spring的MyBatis讀寫分離實現方案

在Web項目開發中,實際生產環境上的數據庫通常都配置成主從環境,並且要求在業務系統中讀寫分離,因此在業務代碼中需要配置至少兩個數據源(讀/寫)。

本文結合實際項目開發經驗(Spring+Mybatis)提出一種實用的數據讀寫分離方案。


主要思路

1.xml配置文件針對讀/寫分別配置不同的Bean,主要包括DataSource(數據源)、SqlSessionFactoryBean以及MapperScannerConfigurer(mapper接口掃描,指定bean生成策略);

2.根據讀寫Bean的生成策略,使用Mapper接口時,通過註解使用不同的bean。


具體實現

1.配置讀寫數據源,由於開發中採用Druid作爲數據庫連接池,因此本文以Druid爲例配置DataSource

主庫配置

<!-- 配置主庫數據源 -->
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${masterdb.url}"/>
        <property name="username" value="${masterdb.user}"/>
        <property name="password" value="${masterdb.password}"/>
        <property name="initialSize" value="${initialSize}"/>
        <property name="minIdle" value="${minIdle}"/>
        <property name="maxActive" value="${maxActive}"/>
        <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}"/>
        <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}"/>
        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="${testWhileIdle}"/>
        <property name="testOnBorrow" value="${testOnBorrow}"/>
        <property name="testOnReturn" value="${testOnReturn}"/>
        <property name="removeAbandoned" value="${removeAbandoned}"/>
        <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}"/>
        <property name="logAbandoned" value="${logAbandoned}"/>
        <property name="filters" value="stat"/>
        <property name="connectionProperties" value="druid.stat.slowSqlMillis=${slowSqlMillis}"/>
  </bean>
<!-- 配置主庫SqlSessionFactory-->
<bean id="masterSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 使用定義的主庫DataSource -->
        <property name="dataSource" ref="masterDataSource" />
        <!-- 定義mapper xml文件路徑-->
        <property name="mapperLocations">
            <list>
                <value>classpath*:mapper/*.xml</value>
            </list>
        </property>
    </bean> 
<!--指定mapper接口掃描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定mapper java接口所在的包路徑-->
        <property name="basePackage" value="com.dao.mapper" />
        <property name="sqlSessionFactoryBeanName" value="masterSqlSessionFactory" />
        <!-- 定義用於連接主庫的 mapper接口bean生成策略-->
        <property name="nameGenerator" >
            <!-- 自定義實現了BeanNameGenerator接口的實現類,bean名稱添加Master後綴 -->
            <bean class="com.utils.MapperBeanNameGenerator">
                <property name="postfix" value="Master"/>
            </bean>
        </property>
    </bean>


從庫配置,與主庫配置基本一致,slaveDataSource需要將defaultReadOnly設置爲true, 具體如下:

<bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${slavedb.url}"/>
        <property name="username" value="${slavedb.user}"/>
        <property name="password" value="${slavedb.password}"/>
        <property name="initialSize" value="${initialSize}"/>
        <property name="minIdle" value="${minIdle}"/>
        <property name="maxActive" value="${maxActive}"/>
        <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}"/>
        <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}"/>
        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="${testWhileIdle}"/>
        <property name="testOnBorrow" value="${testOnBorrow}"/>
        <property name="testOnReturn" value="${testOnReturn}"/>
        <property name="defaultReadOnly" value="true"/> <!-- 設置爲只讀 -->
        <property name="removeAbandoned" value="${removeAbandoned}"/>
        <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}"/>
        <property name="logAbandoned" value="${logAbandoned}"/>
        <property name="filters" value="stat"/>
        <property name="connectionProperties" value="druid.stat.slowSqlMillis=${slowSqlMillis}"/>
    </bean>
	
<!-- 配置從庫SqlSessionFactory-->
<bean id="slaveSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<!-- 使用定義的從庫DataSource -->
        <property name="dataSource" ref="slaveDataSource" />
	<!-- 定義mapper xml文件路徑,可與主庫一致-->
        <property name="mapperLocations">
            <list>
                <value>classpath*:mapper/*.xml</value>
            </list>
        </property>
    </bean>
	
<!--指定mapper接口掃描-->	
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定mapper java接口所在的包路徑,與主庫一致-->
        <property name="basePackage" value="com.dao.mapper" />
        <property name="sqlSessionFactoryBeanName" value="slaveSqlSessionFactory" />
		<!-- 定義用於連接從庫的 mapper接口bean生成策略,bean名稱添加Slave後綴-->
        <property name="nameGenerator" >
            <bean class="com.utils.MapperBeanNameGenerator">
                <property name="postfix" value="Slave"/>
            </bean>
        </property>
  </bean>

MapperBeanNameGenerator源碼

package com.utils;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.util.StringUtils;

public class MapperBeanNameGenerator implements BeanNameGenerator {
    private String postfix = "";

    public MapperBeanNameGenerator() {
    }

    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        String beanClassName = definition.getBeanClassName();
        String beanName = beanClassName;
        int index = beanClassName.lastIndexOf(".");
        if(index != -1) {
            beanName = beanClassName.substring(index + 1);
        }

        return StringUtils.isEmpty(this.postfix) ? beanName : beanName + this.postfix;
    }

    private String uncapitalize(String str) {
        int strLen;
        return str != null && (strLen = str.length()) != 0?(new StringBuilder(strLen)).append(Character.toLowerCase(str.charAt(0))).append(str.substring(1)).toString():str;
    }

    public String getPostfix() {
        return this.postfix;
    }

    public void setPostfix(String postfix) {
        this.postfix = postfix;
    }
}

這樣配置完成後,啓動後bean會掃描com.dao.mapper定義的java接口文件,並分別生成帶master或slave後綴的bean。

2. 業務調用

    @Resource(name = "userMapperMaster")
    UserMapper userMapperMaster;
    @Resource(name = "userMapperSlave")
    UserMapper userMapperSlave;
使用userMapperMaster來調用insert、update相關接口,進行數據插入修改等,使用userMapperSlave調用查詢相關接口。


總結

本文僅僅結合實際工作中進行讀寫分離的一種實現方案,使用簡單,可供需要讀寫分離場景提供一種參考,更多方案可參考其他文章




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章