ssm代码实现mysql读写分离

spring+springMVC+mybatis 实现读写分离

1.首先配置MySQL主从复制

上一篇帖子:配置MySQL主从复制

2.代码实现原理

配置多个数据源;而以前配置的DataSource 只能够从单一的URL中获取连接。在Spring 中提供了一个AbstractRoutingDataSource 类来可以帮我实现多个DataSource。AbstractRoutingDataSource 继承 AbstractDataSource,而 AbstractDataSource 实现了DataSource接口。在 AbstractRoutingDataSource中提供了一个determineTargetDataSource方法可以决定目标的方法使用那个DataSource。我们在使用读写分离只需要定义一个类继承 AbstractRoutingDataSource类重写里面determineCurrentLookupKey方法;此方法就用来动态获取数据源;

3.配置c3p0数据库连接池

这里使用的是c3p0连接池,也可以使用阿里的
spring-dao.xml配置如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置整合mybatis过程-->
    <!--配置数据库相关参数properties的属性-->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!--配置c3p0数据库连接池-->
    <bean id="abstractDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <!--c3p0连接池私有属性-->
        <property name="maxPoolSize" value="30"/>
        <property name="minPoolSize" value="10"/>
        <!--关闭连接后自动commit:false-->
        <property name="autoCommitOnClose" value="false"/>
        <!--获取连接超时时间-->
        <property name="checkoutTimeout" value="10000"/>
        <!--获取连接失败重试次数-->
        <property name="acquireRetryAttempts" value="2"/>
    </bean>

    <!--配置主库-->
    <bean id="masterDataSource" parent="abstractDataSource">
        <!--配置连接池属性-->
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.master.url}"/>
        <property name="user" value="${jdbc.master.username}"/>
        <property name="password" value="${jdbc.master.password}"/>
    </bean>

    <!--配置从库-->
    <bean id="slaveDataSource" parent="abstractDataSource">
        <!--配置连接池属性-->
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.slave.url}"/>
        <property name="user" value="${jdbc.slave.username}"/>
        <property name="password" value="${jdbc.slave.password}"/>
    </bean>

    <!--配置动态数据源-->
    <bean id="dynamicDataSource" class="com.shunli.o2o.dao.split.DynamicDataSource">
        <property name="targetDataSources">
            <map>
                <entry key="master" value-ref="masterDataSource"/>
                <entry key="slave" value-ref="slaveDataSource"/>
            </map>
        </property>
    </bean>

    <!--由于在运行时决定使用哪个数据源,所以使用懒加载-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
        <property name="targetDataSource" ref="dynamicDataSource"/>
    </bean>


    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--注入数据库连接池-->
        <property name="dataSource" ref="dataSource"/>
        <!--配置MyBatis全局配置文件 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--扫描entity包-->
        <property name="typeAliasesPackage" value="com.shunli.o2o.entity"/>
        <!--扫描sql配置文件mapper需要的xml文件-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!--扫描dao接口包,动态实现Dao接口,注入到spring容器中-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--注入sqlSessionFactory-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!--需要扫描的Dao接口包-->
        <property name="basePackage" value="com.shunli.o2o.dao"/>
    </bean>
</beans>

jdbc.properties配置数据库信息

jdbc.driver=com.mysql.jdbc.Driver
jdbc.master.url=jdbc:mysql://192.168.150.138:3306/o2o?useUnicode=true&characterEncoding=utf8
jdbc.master.username=root
jdbc.master.password=root

jdbc.slave.url=jdbc:mysql://192.168.150.137:3306/o2o?useUnicode=true&characterEncoding=utf8
jdbc.slave.username=root
jdbc.slave.password=root

4.创建DynamicDataSource类

package com.shunli.o2o.dao.split;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @author shunli
 * @描述:
 * @create 2020/1/28
 * @since 1.0.0
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDbType();
    }
}

5.创建DynamicDataSourceHolder类

package com.shunli.o2o.dao.split;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author shunli
 * @描述:
 * @create 2020/1/28
 * @since 1.0.0
 */
public class DynamicDataSourceHolder {
    private static Logger logger= LoggerFactory.getLogger(DynamicDataSourceHolder.class);

    /**
     * 保证线程安全,使用线程threadlocal
     */
    private static ThreadLocal<String> contextHolder=new ThreadLocal<String>();

    public static final String DB_MASTER="master";

    public static final String DB_SLAVE="slave";

    /**
     * @return java.lang.String
     * @description 获取连接类型
     */
    public static String getDbType(){
        String db = contextHolder.get();
        if (StringUtils.isBlank(db)) {
            db = DB_MASTER;
        }
        return db;
    }

    /**
     * 设置数据源类型
     * @param str
     */
    public static void setDbType(String str){
        logger.debug("使用数据源类型---"+str);
        contextHolder.set(str);
    }

    /**
     * 清理连接类型
     */
    public static void clearDaType(){
        contextHolder.remove();
    }

}

6.配置mybatis拦截器

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--配置全局属性-->
    <settings>
        <!--使用jdbc的getGenerateKeys获取数据库自增主键值-->
        <setting name="useGeneratedKeys" value="true"/>
        
        <!--使用列标签替换列名 默认:true-->
        <setting name="useColumnLabel" value="true"/>

        <!--开启驼峰命名转换 例如:create_time &#45;&#45;》 createTime-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    
    <plugins>
        <!--自定义的mybatis拦截器-->
        <plugin interceptor="com.shunli.o2o.interceptor.DynamicDataSourceInterceptor"/>
    </plugins>
</configuration>

7.拦截器DynamicDataSourceInterceptor

package com.shunli.o2o.interceptor;

import com.shunli.o2o.dao.split.DynamicDataSourceHolder;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.Locale;
import java.util.Properties;

/**
 * @author shunli
 * @描述:
 * @create 2020/1/28
 * @since 1.0.0
 */
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class})})
public class DynamicDataSourceInterceptor implements Interceptor {

    private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class);
    // 写操作的正则表达式
    private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020|.*update\\u0020.*";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 判断方法是否是被事务管理的
        boolean synchronizationAction = TransactionSynchronizationManager.isActualTransactionActive();
        //获取sql 中的参数
        Object[] objects = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) objects[0];

        //数据源key
        String lookupKey = DynamicDataSourceHolder.DB_MASTER;

        if (!synchronizationAction) {
            // 读方法
            if (mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                //selectKey为自增id查询主键(SELECT LAST_INSERT_ID)方法
                if (mappedStatement.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
                    lookupKey = DynamicDataSourceHolder.DB_MASTER;
                } else {
                    //获取sql 语句
                    BoundSql boundSql = mappedStatement.getSqlSource().getBoundSql(objects[1]);
                    String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replace("[\\t\\n\\r]", " ");
                    if (sql.matches(REGEX)) {
                        lookupKey = DynamicDataSourceHolder.DB_MASTER;
                    } else {
                        lookupKey = DynamicDataSourceHolder.DB_SLAVE;
                    }
                }
            }
        } else {
            lookupKey = DynamicDataSourceHolder.DB_MASTER;
        }
        logger.debug("设置方法[{}] use [{}] Strategy,SqlCommandType [{}]...", mappedStatement.getId(),
                lookupKey, mappedStatement.getSqlCommandType().name());
        DynamicDataSourceHolder.setDbType(lookupKey);
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        //Executor表示含有增删改查的操作 的对象
        if (target instanceof Executor) {
            //有增删改查的操作,就调用拦截方法
            return Plugin.wrap(target, this);
        } else {
            //无增删改查的操作。不做处理
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

你可能感兴趣的:(Sping,MyBatis,mysql)