mybatis-plus+springboot+druid实现读写分离

网上大多读写分离实例,都是需要手动侵入代码修改数据源,本博客主要实现,如何对现有系统,不侵入原有代码,进行读写分离配置。
Mybatis-Plus自带动态数据源配置,但是也需要对手动配置,需要的可以参考官网Mybstis-Plus官网

1、引入依赖

依赖较多,主要还有MybatisPlus单数据源配置的依赖,作者就全部贴上来了。

    <!-- mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.18</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.1.2</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
        <scope>provided</scope>
    </dependency>
2、配置数据源
################DataSource###############
# Druid
#spring.datasource.master
spring.datasource.master.url=jdbc:mysql://127.0.0.1:7001/wj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.master.username=test
spring.datasource.master.password=123456


#spring.datasource.slave
spring.datasource.slave.url=jdbc:mysql://127.0.0.1:7002/wj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
spring.datasource.slave.username=test
spring.datasource.slave.password=123456

#driverClass
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver

# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化连接数量
spring.datasource.initial-size=5
# 最小连接数量
spring.datasource.min-idle=5
# 最大同时连接数量
spring.datasource.max-active=20
# 配置获取连接等待超时的时间,单位毫秒ms
spring.datasource.max-wait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒ms
spring.datasource.time-between-eviction-runs-millis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.min-evictable-idle-time-millis=300000
# SQL查询,用来验证从连接池取出的连接,在将连接返回给调用者之前.如果指定,则查询必须是一个SQL SELECT并且必须返回至少一行记录
# from dual:dual是一个虚拟的表,供测试使用
spring.datasource.validation-query=SELECT 1 FROM DUAL
# 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.
# 注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串
spring.datasource.test-while-idle=true
# 指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个.
# 注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串
spring.datasource.test-on-borrow=false
# 指明是否在归还到池中前进行检验
# 注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串
spring.datasource.test-on-return=false
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.pool-prepared-statements=true
spring.datasource.max-pool-prepared-statement-per-connection-size=20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
#spring.datasource.druid.filter.commons-log.connection-logger-name=stat,wall,log4j
#spring.datasource.druid.filter.stat.log-slow-sql=true
#spring.datasource.druid.filter.stat.slow-sql-millis=2000
spring.datasource.filters=stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.connectionProperties=druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
# 合并多个DruidDataSource的监控数据
spring.datasource.use-global-data-source-stat=true
# 配置DruidStatFilter
spring.datasource.web-stat-filter.enabled=true
spring.datasource.web-stat-filter.url-pattern=/*
spring.datasource.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
# 配置DruidStatViewServlet
spring.datasource.stat-view-servlet.url-pattern=/druid/*
# IP白名单(没有配置或者为空,则允许所有访问)
spring.datasource.stat-view-servlet.allow=127.0.0.1
# IP黑名单 (存在共同时,deny优先于allow)
spring.datasource.stat-view-servlet.deny=
# 禁用HTML页面上的“Reset All”功能
spring.datasource.stat-view-servlet.reset-enable=false
# 登录名
spring.datasource.stat-view-servlet.login-username=admin
# 密码
spring.datasource.stat-view-servlet.login-password=123456
3、读写分离配置

DynamicDataSource.java

package com.imooc.wjtest.config;

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

/**
 * @program: wjtest
 * @description:
 * @author: ZhangKai
 * @create: 2019-11-11 10:32
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {

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

DynamicDataSourceHolder.java

package com.imooc.wjtest.config;

import lombok.extern.slf4j.Slf4j;

/**
 * @program: wjtest
 * @description:
 * @author: ZhangKai
 * @create: 2019-11-11 10:41
 **/
@Slf4j
public class DynamicDataSourceHolder {

    private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static final String DB_MASTER = "master";
    public static final String DB_SLAVE = "slave";

    public static String getDbType() {
        String db = contextHolder.get();
        if (db == null) {
            db = DB_MASTER;
        }
        return db;
    }

    /**
     * 设置线程的dbType
     *
     * @param str
     */
    public static void setDbType(String str) {
        log.debug("所使用的数据源为:" + str);
        contextHolder.set(str);
    }

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

DynamicDataSourceInterceptor.java

package com.imooc.wjtest.config;

import lombok.extern.slf4j.Slf4j;
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.springframework.transaction.support.TransactionSynchronizationManager;

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

/**
 * @program: wjtest
 * @description:
 * @author: ZhangKai
 * @create: 2019-11-11 15:09
 **/
@Slf4j
@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 final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        boolean synchronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
        Object[] objects = invocation.getArgs();
        MappedStatement ms = (MappedStatement) objects[0];
        String lookupKey = DynamicDataSourceHolder.DB_MASTER;
        if (synchronizationActive != true) {
            // 读方法
            if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                // selectKey 为自增id查询主键(SELECT LAST_INSERT_ID())方法,使用主库
                if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
                    lookupKey = DynamicDataSourceHolder.DB_MASTER;
                } else {
                    BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
                    String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                    if (sql.matches(REGEX)) {
                        lookupKey = DynamicDataSourceHolder.DB_MASTER;
                    } else {
                        lookupKey = DynamicDataSourceHolder.DB_SLAVE;
                    }
                }
            }
        } else {
            lookupKey = DynamicDataSourceHolder.DB_MASTER;
        }
        log.debug("设置方法[{}] use [{}] Strategy, SqlCommanType [{}]..", ms.getId(), lookupKey,
                ms.getSqlCommandType().name());
        DynamicDataSourceHolder.setDbType(lookupKey);
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

MyBatisPlusConfig.java

package com.imooc.wjtest.config;

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @program: wjtest
 * @description:
 * @author: ZhangKai
 * @create: 2019-11-11 15:30
 **/

@Slf4j
@Configuration
@MapperScan(basePackages = "com.imooc.wjtest.mapper")
public class MyBatisPlusConfig {

    @Value("${spring.datasource.master.url}")
    private String masterUrl;

    @Value("${spring.datasource.master.username}")
    private String masterUserName;

    @Value("${spring.datasource.master.password}")
    private String masterPassword;

    @Value("${spring.datasource.slave.url}")
    private String slaveUrl;

    @Value("${spring.datasource.slave.username}")
    private String slaveUserName;

    @Value("${spring.datasource.slave.password}")
    private String slavePassword;

    @Value("${spring.datasource.driverClassName}")
    private String driver;

    /**
     * time for connection wait
     */
    @Value("${spring.datasource.max-active}")
    private int maxActive;

    @Value("${spring.datasource.max-wait}")
    private int maxWait;

    @Value("${spring.datasource.initial-size}")
    private int initialSize;

    @Value("${spring.datasource.max-pool-prepared-statement-per-connection-size}")
    private int maxOpenPreparedStatementConnectionSize;

    @Value("${spring.datasource.min-evictable-idle-time-millis}")
    private int minEvictableIdleTimeMillis;

    @Value("${spring.datasource.min-idle}")
    private int minIdle;

    @Value("${spring.datasource.validation-query}")
    private String validationQuery;

    @Value("${spring.datasource.test-while-idle}")
    private boolean testWhileIdle;

    @Value("${spring.datasource.test-on-borrow}")
    private boolean testOnBorrow;

    @Value("${spring.datasource.test-on-return}")
    private boolean testOnReturn;

    @Value("${spring.datasource.pool-prepared-statements}")
    private boolean poolPreparedStatements;

    @Value("${spring.datasource.connectionProperties}")
    private String connectionProperties;

    @Value("${spring.datasource.filters}")
    private String filters;


    @Bean(name = "master")
    public DataSource master() throws SQLException{
        return getDruidDataSource(masterUrl, masterUserName, masterPassword);
    }

    @Bean(name = "slave")
    public DataSource slave() throws SQLException{
        return getDruidDataSource(slaveUrl, slaveUserName, slavePassword);
    }

    private DruidDataSource getDruidDataSource(String url,String userName,String password) throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driver);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(userName);
        druidDataSource.setPassword(password);
        druidDataSource.setMaxActive(maxActive);
        druidDataSource.setInitialSize(initialSize);
        druidDataSource.setMinIdle(minIdle);
        druidDataSource.setMaxWait(maxWait);
        druidDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        druidDataSource.setValidationQuery(validationQuery);
        druidDataSource.setTestWhileIdle(testWhileIdle);
        druidDataSource.setTestOnBorrow(testOnBorrow);
        druidDataSource.setTestOnReturn(testOnReturn);
        druidDataSource.setPoolPreparedStatements(poolPreparedStatements);
        druidDataSource.setMaxOpenPreparedStatements(maxOpenPreparedStatementConnectionSize);
        druidDataSource.setConnectionProperties(connectionProperties);
        druidDataSource.setFilters(filters);
        List<Filter> filters = new ArrayList<>();
        filters.add(createWallFilter());
        druidDataSource.setProxyFilters(filters);
        return druidDataSource;
    }


    @Bean(name = "dynamicDataSource")
    public DynamicDataSource dynamicDataSource(@Qualifier("master") DataSource master,
                                                @Qualifier("slave") DataSource slave){
        Map<Object,Object> targetDataSources = new HashMap<Object, Object>();
        targetDataSources.put(DynamicDataSourceHolder.DB_MASTER, master);
        targetDataSources.put(DynamicDataSourceHolder.DB_SLAVE, slave);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);
        return dataSource;
    }


    /**
     * MybatisPlus扩展分页插件
     * @return
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
    @Bean
    public DynamicDataSourceInterceptor dynamicDataSourceInterceptor(){
        return new DynamicDataSourceInterceptor();
    }

    /**
     * 根据数据源创建SqlSessionFactory
     * @return
     * @throws Exception
     */
    @Bean(name = "SqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory() throws Exception {
        //配置mybatis,对应mybatis-config.xml
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        //懒加载
        LazyConnectionDataSourceProxy p=new LazyConnectionDataSourceProxy();
        p.setTargetDataSource(dynamicDataSource(master(), slave()));
        sqlSessionFactory.setDataSource(p);
        //需要mapper文件时加入扫描
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*Mapper.xml"));
        sqlSessionFactory.setTypeAliasesPackage("com.imooc.wjtest.entity");
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setUseGeneratedKeys(true);
        configuration.setCacheEnabled(false);
        sqlSessionFactory.setConfiguration(configuration);
        //加入上面的两个拦截器
        Interceptor interceptor[]={paginationInterceptor(),dynamicDataSourceInterceptor()};
        sqlSessionFactory.setPlugins(interceptor);
        return sqlSessionFactory.getObject();
    }


    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
        servletRegistrationBean.setServlet(new StatViewServlet());
        servletRegistrationBean.addUrlMappings("/druid/*");
        Map<String, String> initParameters = new HashMap<String, String>();
        // 用户名
        initParameters.put("loginUsername", "admin");
        // 密码
        initParameters.put("loginPassword", "123456");
        // 禁用HTML页面上的“Reset All”功能
        initParameters.put("resetEnable", "false");
        // IP白名单 (没有配置或者为空,则允许所有访问)
        initParameters.put("allow", "");
        // IP黑名单 (存在共同时,deny优先于allow)
//        initParameters.put("deny", "192.168.20.38");
        servletRegistrationBean.setInitParameters(initParameters);
        return servletRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }


    /**
     * 配置事务管理器
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
        return new DataSourceTransactionManager(dataSource);
    }


    /**
     * 自定义过滤器
     * @return
     */
    public WallFilter createWallFilter() {
        WallConfig wallConfig = new WallConfig();
        // 允许一次执行多条语句
        wallConfig.setMultiStatementAllow(true);
        wallConfig.setNoneBaseStatementAllow(true);

        WallFilter wallFilter = new WallFilter();
        wallFilter.setConfig(wallConfig);

        return wallFilter;
    }

}

4、测试
package com.imooc.wjtest;

import com.imooc.wjtest.entity.User;
import com.imooc.wjtest.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
class WjtestApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    void test() {
        try {
            User user = new User("test", "123");
            Integer num = this.userMapper.insert(user);

            User user1 = this.userMapper.selectById(1);

            log.info(user1.getUsername());
            log.info(String.valueOf(num));
        }catch (Exception e){
            e.printStackTrace();
        }

    }

}

结果:
测试结果

5、github

作者偷懒了,只是对配置单纯地贴了代码,如果想进一步了解,或者有不明白的,可以直接访问作者的github下载。该读写分离作者放在一个demo中,下载项目查看即可。码云

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值