springboot+mybatis plus配置多数据源

最近配置多数据源,也是bug频出,在参考了诸多文档,掉了些许头发之后,现在测试OK了,特此分享。本次采用注解的方式,通过AOP来切换不同数据源,也可以通过拦截方法来切换数据源。

!注意点:包的导入和注解的标注,避免jar冲突。

相关版本:jdk1.8,springboot 2.1.3

1》pom.xml导包

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>       
         <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.1.0</version>
        </dependency>     
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>    
         <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>    
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>    

2》yml配置

spring:
  datasource:
         druid:
            db1:
               username: root
               password: 123456
               driverClassName: com.mysql.jdbc.Driver
               url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
            db2:
               username: root
               password: 123456
               driverClassName: com.mysql.jdbc.Driver
               url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
            db3:
               username: root
               password: 123456
               driverClassName: com.mysql.jdbc.Driver
               url: jdbc:mysql://localhost:3306/db3?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai

3》数据源相关设置

1、DatasourceConstants类:

/**
 * 数据源常量
 */
public class DatasourceConstants {

        public static final String DATASOURCE1 = "datasource1";
        public static final String DATASOURCE2 = "datasource2";
        public static final String DATASOURCE3 = "datasource3";
}

2、DataSourceContextHolder类:

/**
 * 数据源容容器
 *
 * 
 */
public class DataSourceContextHolder {

    public static final ThreadLocal<String> HOLDER = new ThreadLocal<>();

    /**
     * 获取数据源名
     * @return
     */
    public static String getDataSource(){
        return (String) HOLDER.get();
    }

    /**
     * 设置数据源名
     * @param dataSourceName
     */
    public static void setDataSource(String dataSourceName){
        HOLDER.set(dataSourceName);
    }

    /**
     * 移除数据源名
     */
    public  static void removeDataSource(){
        HOLDER.remove();
    }
}

3、DynamicDataSource类,这是很关键的一步,继承自AbstractRoutingDataSource抽象类,里面有个重要的方法:部分源码

/**
     * Retrieve the current target DataSource. Determines the
     * {@link #determineCurrentLookupKey() current lookup key}, performs
     * a lookup in the {@link #setTargetDataSources targetDataSources} map,
     * falls back to the specified
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
     * @see #determineCurrentLookupKey()
     */
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        }
        return dataSource;
    }

我们需要重写determineCurrentLookupKey()方法:


import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * 获取动态数据源
 * 
 * 
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

4、自定义MyDataSource 注解:

/**
 * 数据源注解
 * @author yangxiaoguang
 * @date 2020/5/14
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface MyDataSource {
    String value() default "";
}

5、DynamicDataSourceAspect切面类:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 数据源切面  需要在事务@Transactional之前运行
 * 
 * 
 */
@Component
@Aspect
@Order(-1)
public class DynamicDataSourceAspect {

    //切点表达式
    @Pointcut("@annotation(com.springboot.dataSource.MyDataSource)")
    public void  pointcut(){

    }

    @Before("pointcut()")
    public void  beforeMethod(JoinPoint joinPoint) throws NoSuchMethodException {
        //获取class对象
        Class<?> clazz = joinPoint.getTarget().getClass();
        //获取方法名
        String methodName = joinPoint.getSignature().getName();
        //获取方法参数类型
        Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        //获取到Method类
        Method method = clazz.getMethod(methodName, parameterTypes);
        //获取到注解
        MyDataSource annotation = method.getAnnotation(MyDataSource.class);
        //获取注解上的值
        String dataSourceName = annotation.value();
        //设置数据源
        DataSourceContextHolder.setDataSource(dataSourceName);
    }

    @After("pointcut()")
    public void afterSwitchDS(JoinPoint joinPoint) {
        DataSourceContextHolder.removeDataSource();
    }
}

4》MybatisPlusConfig 配置文件:

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;

import com.springboot.dataSource.DatasourceConstants;
import com.springboot.dataSource.DynamicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;

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


@EnableTransactionManagement
@Configuration
@MapperScan("com.springboot.modules.*.mapper")
public class MybatisPlusConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid.db1")
    public DataSource db1() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid.db2")
    public DataSource db2() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid.db3")
    public DataSource db3() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 动态数据源配置
     * @return
     */
    @Bean
    @Primary
    public DataSource setDynamicDataSource( DataSource db1,  DataSource db2, DataSource db3) {

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        //设置默认的数据源,不设置会报错
        dynamicDataSource.setDefaultTargetDataSource(db1);

        //配置多数据源,加入其他数据源到map中v
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DatasourceConstants.DATASOURCE1, db1);
        targetDataSources.put(DatasourceConstants.DATASOURCE2, db2);
        targetDataSources.put(DatasourceConstants.DATASOURCE3, db3);
        dynamicDataSource.setTargetDataSources(targetDataSources);

        return dynamicDataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {

        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(setDynamicDataSource(db1(), db2(),db3()));

        //数据库相关设置
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml"));
        sqlSessionFactory.setConfiguration(configuration);
        return sqlSessionFactory.getObject();
    }
}

5》测试:

controller层:这里整合swagger ui

   @ApiOperation(value = "查询个人学生信息")
    @PostMapping(value = "/findStudent")
    public ResponseData findStudent(@RequestBody StudentReq studentReq){
        return ResponseData.okData(studentService.findStudent(studentReq));
    }

service层:

   @Override
    @MyDataSource("datasource2")
    public Student findStudent(StudentReq studentReq) {
        return studentMapper.selectOne(new QueryWrapper<>());
    }

数据库:

db1:student表

db2:student表

db3:student表

测试结果:

默认数据源db1,切换数据源db2:

 默认数据源db1,切换数据源db3:

 以上就是多数据源的基本配置了,有啥不当可以交流交流。

参考文档:

https://mp.baomidou.com/guide/dynamic-datasource.html

https://yq.aliyun.com/articles/634622

原文地址:https://www.cnblogs.com/tdyang/p/12895545.html