基于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调用查询相关接口。


总结

本文仅仅结合实际工作中进行读写分离的一种实现方案,使用简单,可供需要读写分离场景提供一种参考,更多方案可参考其他文章




评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值