mybatis-plus+springboot实现读写分离

mybatis-plus是mybatis的增强版,他相较于原始的mybatis少许多配置,而且使用也足够简单,由于最近写的一篇文章是关于mysql主从配置的,所以把代码层面的编写也加上来。mybatis-plus的官方地址,mybatis-plus是兼容mybatis的,所以不需要过多担心迁移后出现问题,相反还可以提高开发速度

接下来我们开始配置

首先引入依赖:

        
            com.baomidou
            mybatis-plus-boot-starter
            3.1.1
        
        
            mysql
            mysql-connector-java
        
     

然后对配置数据源

spring:
  datasource:
   hikari:
     master:
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/mptest?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
     slave:
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3307/mptest?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456

这里要注意是jdbc-url,而且链接还要加上时区,不然会报奇奇怪怪的错误,当时就是被这个小问题卡了挺久

网上很多都是基于aop在service层上实现分离,这里因为我以前在慕课网的翔仔老师(我最喜欢的讲师,讲课很亲切很清晰)实战课上学过ssm的基于拦截器的配置,如今把这个技术对接上springboot、mybatis-plus做一个回顾。

基本的思路就是当实现一个mybatis的拦截器,对增删改查操作进行拦截,再根据语句类型分配给不同的数据源

然后看一下实现方式

package com.gdut.imis.esclientdemo.dyna;

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

/**
 * @author lulu
 * @Date 2019/6/8 12:13
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDbType();
    }
}

这个类用来决定使用哪个数据源,我们新建一个holder来存放相应的datasource

package com.gdut.imis.esclientdemo.dyna;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lulu
 * @Date 2019/6/8 12:14
 */
@Slf4j
public class DynamicDataSourceHolder {
    private static ThreadLocal contextHolder = new ThreadLocal<>();
    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;
    }

    public static void setDBType(String str) {
        log.info("数据源为" + str);
        contextHolder.set(str);
    }

    public static void clearDbType() {
        contextHolder.remove();
    }
}

如果有多个从数据库,可以用一个枚举或者集合封装把从数据库信息添加进去

拦截器实现:

package com.gdut.imis.esclientdemo.dyna;

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;

/**
 * @author lulu
 * @Date 2019/6/8 12:18
 */
@Slf4j
//指定拦截哪些方法,update包括增删改
@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();
        String lookupKey=DynamicDataSourceHolder.DB_MASTER;
        if(!synchronizationActive){
           Object[] objects=invocation.getArgs();
            MappedStatement ms=(MappedStatement)objects[0];
            if(ms.getSqlCommandType().equals(SqlCommandType.SELECT)){
                //如果selectKey为自增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;
        }
        DynamicDataSourceHolder.setDBType(lookupKey);
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        //增删改查的拦截,然后交由intercept处理
        if(target instanceof Executor){
            return Plugin.wrap(target,this);
        }else{
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

config配置

package com.gdut.imis.esclientdemo.config;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.gdut.imis.esclientdemo.dyna.DynamicDataSource;
import com.gdut.imis.esclientdemo.dyna.DynamicDataSourceHolder;
import com.gdut.imis.esclientdemo.dyna.DynamicDataSourceInterceptor;
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.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lulu
 * @Date 2019/6/8 16:12
 */
@Configuration // 该注解类似于spring配置文件
@MapperScan(basePackages = "com.gdut.imis.esclientdemo.dao*")
public class MyBatisPlusConfig {
    /**
     * 配置数据源
     * @return
     */
    @Bean(name = "master")
    @ConfigurationProperties(prefix = "spring.datasource.hikari.master")
    public DataSource master() {
        return DataSourceBuilder.create().build();
    }
    @Bean(name = "slave")
    @ConfigurationProperties(prefix = "spring.datasource.hikari.slave")
    public DataSource slave() {
        return DataSourceBuilder.create().build();
    }


    @Primary
    @Bean(name = "dynamicDataSource")
    public DynamicDataSource dataSource(@Qualifier("master") DataSource master,
                                        @Qualifier("slave") DataSource slave) {
        Map targetDataSource = new HashMap<>();
        targetDataSource.put(DynamicDataSourceHolder.DB_MASTER, master);
        targetDataSource.put(DynamicDataSourceHolder.DB_SLAVE, slave);
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSource);
        return dataSource;
    }


    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
    @Bean
    public DynamicDataSourceInterceptor dynamicDataSourceInterceptor(){
        return new DynamicDataSourceInterceptor();
    }
    /**
     * 根据数据源创建SqlSessionFactory
     */
    @Bean(name = "SqlSessionFactory")
    public SqlSessionFactory test1SqlSessionFactory()
            throws Exception {
        //配置mybatis,对应mybatis-config.xml
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        //懒加载
        LazyConnectionDataSourceProxy p=new LazyConnectionDataSourceProxy();
        p.setTargetDataSource(dataSource(master(),slave()));
        sqlSessionFactory.setDataSource(p);
        //需要mapper文件时加入扫描,sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*/*Mapper.xml"));
        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 DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
        return new DataSourceTransactionManager(dataSource);
    }

}

然后mybatis-plus基础使用非常简单,基本方法只要继承baseMapper就可以了

编写一个小例子

@Test
	public void insert(){
		User u=new User();
		u.setAge(21);
		u.setEmail("[email protected]");
		u.setId(3L);
		u.setName("tjl");
		userMapper.insert(u);
		userMapper.selectList(null).forEach(System.out::println);
	}

结果

mybatis-plus+springboot实现读写分离_第1张图片

此致,整合完毕,github地址(由于本人较懒,没有新建另一个项目可能有点奇怪),官方文档也有更简便的搭建方法

 

你可能感兴趣的:(mybatis-plus)