读写分离实现自动切换数据库(基于mybatis拦截器)

仍是动态数据源的那一套,先要继承java

AbstractRoutingDataSource

1spring

package com.statistics.util;

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

/**
 * 用户 :LX
 * 建立时间: 2018/7/4. 23:49
 * 地点:广州
 * 目的: 动态数据源,若是是作读写分离,能够用这个类来作读写的自动切换数据库,可是这里只是测试
 * 结果:
 * 1 AbstractRoutingDataSource用来作动态数据源切换的类,要继承他才行
 * 2 建立 DynamicDataSourceHolder 类,用来作操做数据源
 * 3 编写 DynamicDataSourceInterceptor 类来自动切换数据源
 * 4 在mybatis的配置文件中去设置 DynamicDataSourceInterceptor 拦截器
 * 5 spring中对数据源进行配置
 * 6 写好注解,哪些要拦截
 */

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDbType();
    }
}

而后建立数据库操做类sql

package com.statistics.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * 用户 :LX
 * 建立时间: 2018/7/4. 23:52
 * 地点:广州
 * 目的: 动态数据源的操做
 * 结果:
 */
public class DynamicDataSourceHolder {
    //日志对象
    private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceHolder.class);

    /**
     * 线程安全的本地线程类
     */
    private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    /**
     * 主数据库
     */
    public static final String DB_MASTER = "master";

    /**
     * 从库
     */
    public static final String DB_SLAVE = "slave";

    /**
     * 获取数据源
     * @return
     */
    public static String getDbType(){
        String db = contextHolder.get();
        logger.debug("getDbType方法中从线程安全的里面获取到:" + db);
        if (db == null){
            db = DB_MASTER;
        }
        return db;
    }

    /**
     * 注入线程的数据源
     * @param str
     */
    public static void setDbType(String str){
        logger.debug("所注入使用的数据源:" + str);
        contextHolder.set(str);
    }

    /**
     * 清理链接
     */
    public static void clearDBType(){
        contextHolder.remove();
    }
}

最关键的地方就是下面的这个类数据库

package com.statistics.util;

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;

/**
 * 用户 :LX
 * 建立时间: 2018/7/5. 0:02
 * 地点:广州
 * 目的: 数据源的拦截器,靠这个来进行切换读写时候的数据源
 * 结果:
 *  Interceptor 就是mybatis的 Interceptor,mybatis的拦截器
 */

//@Intercepts标记了这是一个Interceptor,而后在@Intercepts中定义了两个@Signature,即两个拦截点。第一个@Signature咱们定义了该Interceptor将拦截Executor接口中参数类型
// 为MappedStatement、Object、RowBounds和ResultHandler的query方法;第二个@Signature咱们定义了该Interceptor将拦截StatementHandler中参数类型为Connection的prepare方法。
//   只要拦截了update和query便可,全部的增删改查都会封装在update和query中
@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);

    /**
     * 判断是插入仍是增长仍是删除之类的正则, u0020是空格
     */
    private static final String regex = ".*insert\\u0020.*|.*delete\\u0020.*|.update\\u0020.*";

    /**
     * 咱们要操做的主要拦截方法,什么状况下去拦截,就看这个了
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //判断当前是否有实际事务处于活动状态 true 是
        boolean synchronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
        //获取sql的资源变量参数(增删改查的一些参数)
        Object[] objects = invocation.getArgs();
        //MappedStatement 能够获取到究竟是增长仍是删除 仍是修改的操做
        MappedStatement mappedStatement = (MappedStatement) objects[0];
        //用来决定datasource的
        String lookupKey = DynamicDataSourceHolder.DB_MASTER;

        if (synchronizationActive != true){
            //当前的是有事务的====================Object[0]=org.apache.ibatis.mapping.MappedStatement@c028cc
            logger.debug("当前的是有事务的====================Object[0]=" + objects[0]);
            //读方法,说明是 select 查询操做
            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 sqlstr = boundSql.getSql();
                    //toLowerCase方法用于把字符串转换为小写,replaceAll正则将全部的制表符转换为空格
                    String sql = sqlstr.toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                    //sql是这个===================:select top 1 * from cipentinfo where regno=?
                    logger.debug("sql是这个===================:" + sql);

                    //使用sql去匹配正则,看他是不是增长、删除、修改的sql,若是是则使用主库
                    if (sql.matches(regex)){
                        lookupKey = DynamicDataSourceHolder.DB_MASTER;
                    } else {
                        //从读库(从库),注意,读写分离后必定不能将数据写到读库中,会形成很是麻烦的问题
                        lookupKey = DynamicDataSourceHolder.DB_SLAVE;
                    }
                }
            }
        } else {
            //非事务管理的用主库
            lookupKey = DynamicDataSourceHolder.DB_MASTER;
        }

        //设置方法[slave] ues [SELECT] Strategy,SqlCommanType[SELECT],sqlconmantype[{}]......................... cipentinfoNamespace.selectOneByRegNo
        logger.debug("设置方法[{}] ues [{}] Strategy,SqlCommanType[{}],sqlconmantype[{}]......................... " + mappedStatement.getId(), lookupKey, mappedStatement.getSqlCommandType().name(), mappedStatement.getSqlCommandType());
        //最终决定使用的数据源
        DynamicDataSourceHolder.setDbType(lookupKey);
        return invocation.proceed();
    }

    /**
     * 返回封装好的对象,决定返回的是本体仍是编织好的代理
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        //Executor是mybatis的,全部的增删改查都会通过这个类
        if (target instanceof Executor){
            //若是是Executor 那就进行拦截
            return Plugin.wrap(target, this);
        } else {
            //不然返回本体
            return target;
        }
    }

    /**
     * 类初始化的时候作一些操做
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {

    }
}

上面已经会根据语句自动进行选择数据库的 工做apache

而后在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>

    <!--mybatis的配置文件-->


    <plugins>
        <!--配置自定义的拦截器,这是mybatis自带的 -->
        <plugin interceptor="com.statistics.util.DynamicDataSourceInterceptor" />
    </plugins>

</configuration>

最后将spring配置多数据源那里注册bean(session

DynamicDataSource

)类mybatis

将全部的数据源管理起来,注意一点,咱们本身定义的数据源的名字必定要和spring配置这里的数据源的对应,不然找不到数据源就尴尬了。这一部分就省略了,不懂的百度app

相关文章
相关标签/搜索