ssm实现mysql读写分离

用于以后方便查看,借用:https://blog.csdn.net/weixin_43882034/article/details/84996443
1.创建 DyncmicDataSourceHolder 类
package cn.ljs.dyncmic;

import com.alibaba.dubbo.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 在读写分离的实现上,
 * Spring为我们提供了路由数据源的AbstractRoutingDataSource抽象类,
 * 我们只需编写一个新的类继承它,
 * 即可在不改变原有的代码的基础上实现读写分离
 */
public class DyncmicDataSourceHolder {
    private static Logger logger= LoggerFactory.getLogger(DyncmicDataSourceHolder.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);
        System.err.println("使用数据源类型---"+str);
        contextHolder.set(str);
    }

    /**
     * 清理连接类型
     */
    public static void clearDaType(){
        contextHolder.remove();
    }
}
2.创建 DynamicDataSource,继承AbstractRoutingDataSource
package cn.ljs.dyncmic;

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

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DyncmicDataSourceHolder.getDbType();
    }
}
3 创建DyncmicDataSourceInterceptor,mybatis拦截器
package cn.ljs.dyncmic;
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;

/**
 * 创建DyncmicDataSourceInterceptor,mybatis拦截器
 */
@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 DyncmicDataSourceInterceptor implements Interceptor {

    private static Logger logger = LoggerFactory.getLogger(DyncmicDataSourceInterceptor.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 = DyncmicDataSourceHolder.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 = DyncmicDataSourceHolder.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)) {//insert、update、delete
                        lookupKey = DyncmicDataSourceHolder.DB_MASTER;
                    } else {//select
                        lookupKey = DyncmicDataSourceHolder.DB_SLAVE;
                    }
                }
            }
        } else {
            lookupKey = DyncmicDataSourceHolder.DB_MASTER;
        }
        logger.debug("设置方法[{}] use [{}] Strategy,SqlCommandType [{}]...", mappedStatement.getId(),
                lookupKey, mappedStatement.getSqlCommandType().name());
        DyncmicDataSourceHolder.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) {

    }
}
4.修改mybatis-config,引入拦截器
<?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>
        <!-- 打印查询语句 -->
         <!--<setting name="logImpl" value="STDOUT_LOGGING" />-->
        <!--解决,查询返回结果含null没有对应字段值问题-->
        <setting name="callSettersOnNulls" value="true"/>
    </settings>

    <!-- 设置别名 -->
    <typeAliases>
        <package name="cn.ljs.entity" />
    </typeAliases>
    
    <plugins>
        <plugin interceptor="cn.ljs.dyncmic.DyncmicDataSourceInterceptor"></plugin>
    </plugins>
</configuration>

5,修改mybatis配置文件,

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

 
     
    <!-- 配置base数据源 -->
    <!-- Druid -->
    <bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource"
        init-method="init"  destroy-method="close">
        <!-- 基本属性 url、user、password -->

        <!-- 本地 -->
        <property name="url" value="jdbc:mysql://localhost:3306/new_cgpt?useUnicode=true&amp;characterEncoding=utf8&amp;allowMultiQueries=true" />
        <property name="username" value="root" />
        <property name="password" value="root" />
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="0" />
        <property name="minIdle" value="0" />
        <property name="maxActive" value="100" />

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000" />

        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000" />

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000" />

        <property name="validationQuery" value="select 1 from dual" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />

        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
        
        <!-- 对于长时间不使用的连接强制关闭 -->
        <property name="removeAbandoned" value="true" />
    </bean>

    <bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init"  destroy-method="close">
        <!-- 本地 -->
        <property name="url" value="jdbc:mysql://localhost:3306/cgpt?useUnicode=true&amp;characterEncoding=utf8&amp;allowMultiQueries=true" />
        <property name="username" value="root" />
        <property name="password" value="root" />
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="0" />
        <property name="minIdle" value="0" />
        <property name="maxActive" value="100" />

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000" />

        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000" />

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000" />

        <property name="validationQuery" value="select 1 from dual" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />

        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />

        <!-- 对于长时间不使用的连接强制关闭 -->
        <property name="removeAbandoned" value="true" />
    </bean>

    <!--配置动态数据源-->
    <bean id="dynamicDataSource" class="cn.ljs.dyncmic.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 bean="dynamicDataSource"/>
        </property>
    </bean>

    <!-- 注册SqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        
        <!-- 自动扫描mappers.xml文件 -->
        <property name="mapperLocations" value="classpath:cn/ljs/mapper/*.xml" />
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    </bean>
    
    <!-- DAO接口所在包名,Spring会自动查找其下的类 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.ljs.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>
</beans>
原文地址:https://www.cnblogs.com/ljmm/p/13712785.html