SpringBoot+Mybatis 整合多数据源 (druid)

作为一个合格的架构师,今天这篇文章算是划水,但是又是不得不会的。

我的项目是SpringBoot+Mybatis通用Mapper + 多数据源(druid),如果连基本Mybatis都没整合的,请先见我这篇文章:

SpringCloud+Nacos+Mybatis+Redis+Kafka 微服务整套框架环境搭建​githubs.xyz

 

准备两个数据源

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
    # 个微 数据源
      wechat:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://aaaaa:3306/logdb?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
        username: wangfeng
        password: aaaa
        maxActive: 100
    # 企微 数据源
      enterprise:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://bbbbb:4000/logdb?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
        username: wangfeng
        password: aaaa
        maxActive: 100    
  • 第一个数据源配置:spring.datasource.druid.wechat
  • 第二个数据源配置: spring.datasource.druid.enterprise

 

druid连接池版本:

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid</artifactId>
	<version>1.2.5</version>
</dependency>

SpringBoot项目代码

首先定义一个ThreadLocal类 来存储当前要切换到什么数据源:

public class HandleDataSource {
    public static final ThreadLocal<String> holder = new ThreadLocal<String>();
    /**
     * title : 绑定当前线程数据源
     * 
     * @param key
     */
    public static void putDataSource(String datasource) {
        holder.set(datasource);
    }

    /**
     * title : 获取当前线程的数据源
     * 
     * @return
     */
    public static String getDataSource() {
        return holder.get();
    }
    public static void clear() {
        holder.remove();
    }
}

定义一个DataSource注解,用在service方法里面,指定该方法走哪个数据源:

/***
 * title: 指定改方法走哪个数据源
 * 
 * @author HadLuo
 * @since JDK1.7
 * @history 2019年6月14日 新建
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
    /***
     * 
     * title: 指定该方法使用的数据源 名称
     *
     * @return
     * @author HadLuo 2021-3-11 10:28:37
     */
    String value();
}

定义两个数据源类型名称:

public class Type{
    /***
     * title : 企业微信 数据源名称
     */
    public static final String EnterpriseDataSource = "enterprise";

    /***
     * title : 个微 数据源名称
     */
    public static final String WechatDataSource = "wechat";
}

然后向spring注入两个数据源实例bean(就在你的MybatisConfigure类中定义):

/**
     * title: 个微数据源配置
     */
    @Bean(name = Type.WechatDataSource, initMethod = "init", destroyMethod = "close")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.druid.wechat")
    public DataSource masterDataSource() {
        DataSource dataSource = DataSourceBuilder.create(this.getClass().getClassLoader())
            .type(com.alibaba.druid.pool.DruidDataSource.class).build();
        return dataSource;
    }

    /**
     * title: 企业微信数据源配置
     */
    @Bean(name = Type.EnterpriseDataSource, initMethod = "init", destroyMethod = "close")
    @ConfigurationProperties(prefix = "spring.datasource.druid.enterprise")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create(this.getClass().getClassLoader())
            .type(com.alibaba.druid.pool.DruidDataSource.class).build();
    }

spring.datasource.druid.wechat , spring.datasource.druid.enterprise 就是文章开头yml数据源配置。两个数据源bean的名称 就是为 我们的Type中的名称。

 

定义spring动态切换的数据源(可以在你的MybatisConfigure类中定义):

    // 向spring中注册动态数据源,这样调用mapper的数据库操作就会自动切换数据源
    @Bean(name = "dynamicDataSource")
    public DynamicDataSource dataSource(@Qualifier(Type.WechatDataSource) DataSource wechatDataSource,
        @Qualifier(Type.EnterpriseDataSource) DataSource enterpriseDataSource) {
        Map<Object, Object> allDataSources = new HashMap<>();
        // 设置了两个数据源:bean名称 Type.WechatDataSource   Type.EnterpriseDataSource
        allDataSources.put(WechatDataSource, wechatDataSource);
        allDataSources.put(EnterpriseDataSource, enterpriseDataSource);
        // 默认是 Type.WechatDataSource 这个数据源
        return new DynamicDataSource(wechatDataSource, allDataSources);
    }
    /***
     * 
     * title: spring 动态数据源路由类
     *
     * @author HadLuo
     * @date 2021-3-11 11:07:45
     */
    public static class DynamicDataSource extends AbstractRoutingDataSource {
        public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            super.setTargetDataSources(targetDataSources);
            super.afterPropertiesSet();
        }
        @Override
        protected Object determineCurrentLookupKey() {
            // 核心: 调用mybatis的mapper操作数据库时,会先通过这个方法来选择用哪个数据源
           // 这里我们 取的 是 Threadlocal的,具体在哪设置,后面一个切面会讲到
            return HandleDataSource.getDataSource();
        }

    }

注意注入到spring的名称为 :dynamicDataSource 。

 

Mybatis的SqlSessionFactory 设置 为 上面的动态数据源:

    @Bean
    public SqlSessionFactoryBean createSqlSessionFactoryBean(
        @Autowired @Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        /** 设置动态的 数据源 dynamicDataSource */
        sqlSessionFactoryBean.setDataSource(dynamicDataSource);
       // 设置mapper,pojo路径等 我就不说了........
        return sqlSessionFactoryBean;
    }

 

最后就是我们的切面

@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("execution(public * com.uc..*.*(..))&&@annotation(dataSource)")
    public void pointCut(DataSource dataSource) {}

    /***
     * title: 执行之后清除数据源
     * 
     * @param point
     * @param processor
     * @author HadLuo 2019年4月18日 新建
     */
    @After("pointCut(dataSource)")
    public void after(JoinPoint point, DataSource dataSource) {
        HandleDataSource.clear();
    }

    /***
     * 执行之前 ,
     * 
     * @param point
     * @param processor
     * @author HadLuo 2019年4月18日 新建
     */
    @Before("pointCut(dataSource)")
    public void before(JoinPoint point, DataSource dataSource) {
        Object target = point.getTarget();
        String method = point.getSignature().getName();
        Class<?>[] parameterTypes = ((MethodSignature)point.getSignature()).getMethod().getParameterTypes();
        Method m;
        try {
            m = target.getClass().getMethod(method, parameterTypes);
            if (m != null && m.isAnnotationPresent(DataSource.class)) {
                DataSource data = m.getAnnotation(DataSource.class);
                // 数据源放到当前线程中
                HandleDataSource.putDataSource(data.value());
            } else {
                HandleDataSource.clear();
            }
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
    }
}

这个切面就是拦截 配置了@DataSource 注解的方法,将注解的value 设置到 HandleDataSource 的ThreadLocal中。

还记得我们上面定义的动态数据源bean吗:

    public static class DynamicDataSource extends AbstractRoutingDataSource {
        public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            super.setTargetDataSources(targetDataSources);
            super.afterPropertiesSet();
        }
        @Override
        protected Object determineCurrentLookupKey() {
            // 核心: 调用mybatis的mapper操作数据库时,会先通过这个方法来选择用哪个数据源
           // 这里我们 取的 是 Threadlocal的,具体在哪设置,后面一个切面会讲到
            return HandleDataSource.getDataSource();
        }

    }

于是就从 ThreadLocal 中 取到了 要用哪个数据源。

 

业务Service测试

 @Override
 @DataSource(Type.EnterpriseDataSource) // 企业微信数据源
public void test() {
     System.err.println("当前数据源: " + HandleDataSource.getDataSource());
     GroupSendMessage message = new GroupSendMessage();
      // 插入企业微信 的数据源
     App.getBean(GroupSendMessageMapper.class).insertSelective(message);
}

会打印: 当前数据源: enterprise 。 并且数据会插入到:

    # 企微 数据源
      enterprise:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://bbbbb:4000/logdb?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
        username: wangfeng
        password: aaaa
        maxActive: 100   

这个数据源里面。

 

本文完~

 

推荐一个Java架构师博客,带你一起实践写架构:

 

Java架构师修炼​githubs.xyz

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值