Springboot+mybatis+druid注解模式动态切换多数据源

通常情况下一个项目里面只连接一个数据库就可以,但是也有很多种情况需要配置多个数据源的场景,本篇就讲解下使用spring boot、mybatis、druid配置多数据源的方式。

1.在pom文件中引入需要的依赖

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.20</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>
        <dependency>
            <groupId>cn.easyproject</groupId>
            <artifactId>ojdbc7</artifactId>
            <version>12.1.0.2.0</version>
        </dependency>

2.创建一个持有数据源上下文的DataSourceContextHolder类

package com.yaomy.control.aop.datasource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @Description: 线程持有数据源上线文
 * @Author yaomy
 * @Version: 1.0
 */
public class DataSourceContextHolder  {
    /**
     * 当前线程对应的数据源
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    /**
     * 存储当前系统加载的数据源的查找键(look up key)KEY
     */
    public static final Set<Object> ALL_DATA_SOURCE_KEY = new HashSet<>();

    /**
     * 设置当前线程持有的数据源
     */
    public static void setDataSource(String dataSource){
        if(isExist(dataSource)){
            CONTEXT_HOLDER.set(dataSource);
        } else {
            throw new NullPointerException(StringUtils.join("数据源查找键(Look up key)【", dataSource,"】不存在"));
        }
    }
    /**
     * 获取当前线程持有的数据源
     */
    public static String getDataSource(){
        return CONTEXT_HOLDER.get();
    }

    /**
     * 删除当前线程持有的数据源
     */
    public static void remove(){
        CONTEXT_HOLDER.remove();
    }

    /**
     * 判断数据源在系统中是否存在
     */
    public static boolean isExist(String dataSource){
        if(StringUtils.isEmpty(dataSource)){
            return false;
        }
        if(ALL_DATA_SOURCE_KEY.contains(dataSource)){
            return true;
        }
        return false;
    }
}

为了线程的安全使用ThreadLocal来存储数据源查找键(Look Up key)

3.多数据源实现类,该类需要继承AbstractRoutingDataSource抽象类来实现通过查找键(Look Up Key)动态的切换数据源

package com.yaomy.control.aop.datasource;

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

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

/**
 * @Description: 抽象的数据源实现(javax.sql.DataSource),该实现基于查找键将getConnection()路由到各种目标数据源,目标数据源通常但是不限于通过一些线程绑定
 * 的事务上下文来确定
 * @Author yaomy
 * @Version: 1.0
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * 私有的构造函数
     * @param defaultTargetDataSource 默认数据源
     * @param targetDataSources 所有的数据源
     */
    private DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources){
        /**
         * 如果存在默认数据源,指定默认的目标数据源;映射的值可以是javax.sql.DataSource或者是数据源(data source)字符串;
         * 如果setTargetDataSources指定的数据源不存在,将会使用默认的数据源
         */
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        /**
         * 指定目标数据源的Map集合映射,使用查找键(Look Up Key)作为Key,这个Map集合的映射Value可以是javax.sql.DataSource或者是数据源(data source)字符串;
         * 集合的Key可以为任何数据类型,当前类会通过泛型的方式来实现查找,
         */
        super.setTargetDataSources(targetDataSources);
        /**
         * 指定对默认的数据源是否应用宽松的回退,如果找不到当前查找键(Look Up Key)的特定数据源,就返回默认的数据源,默认为true;
         */
        super.setLenientFallback(true);
        /**
         * 设置DataSourceLookup为解析数据源的字符串,默认是使用JndiDataSourceLookup;允许直接指定应用程序服务器数据源的JNDI名称;
         */
        super.setDataSourceLookup(null);
        /**
         * 将设置的默认数据源、目标数据源解析为真实的数据源对象赋值给resolvedDefaultDataSource变量和resolvedDataSources变量
         */
        super.afterPropertiesSet();
        /**
         * 将数据源查找键(look up key)KEY存储进入静态变量中,供其它地方校验使用
         */
        DataSourceContextHolder.ALL_DATA_SOURCE_KEY.addAll(targetDataSources.keySet());
    }

    /**
     * 构件DynamicDataSource对象静态方法
     */
    public static DynamicDataSource build(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources){
        return new DynamicDataSource(defaultTargetDataSource, targetDataSources);
    }

    /**
     * 确定当前线程的查找键,这通常用于检查线程绑定事物的上下文,允许是任意的键(Look Up Key),
     * 返回的查找键(Look Up Key)需要与存储的查找键(Look Up key)类型匹配
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }

}

4.定义动态切换的注解类TargetDataSource

package com.yaomy.control.aop.annotation;

import com.yaomy.control.aop.constant.DbType;

import java.lang.annotation.*;

/**
 * @Description: 自定义注解,切换数据源,默认主数据源primary
 * @Version: 1.0
 */
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    String value() default DbType.DEFAULT_DATASOURCE;
}

5.上面几步把基础已经搭建完成了,这一步通过spring boot自带AOP接口MethodInterceptor进行动态的切换数据源

package com.yaomy.control.aop.advice;

...

/**
 * @Description: 在接口到达具体的目标即控制器方法之前获取方法的调用权限,可以在接口方法之前或者之后做Advice(增强)处理
 * @Version: 1.0
 */
@Component
public class ControllerAdviceInterceptor implements MethodInterceptor {
    ...

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        //数据源切换aop处理
        return dataSourceHandler(invocation);
      
    }
    /**
     * 数据源切换AOP拦截处理
     */
    private Object dataSourceHandler(MethodInvocation invocation) throws Throwable{
        //获取Method对象
        Method method = invocation.getMethod();
        //数据源切换开始
        TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);
        //获取注解标注的数据源
        String dataSource = targetDataSource.value();
        //判断当前的数据源是否已经被加载进入到系统当中去
        if(!DataSourceContextHolder.isExist(dataSource)){
            throw new NullPointerException(StringUtils.join("数据源查找键(Look up key)【", dataSource,"】不存在"));
        }
        try{
            LoggerUtil.info(invocation.getThis().getClass(), StringUtils.join(MSG_CONTROLLER, invocation.getThis().getClass(), ".", method.getName(), MSG_DATASOURCE_START, dataSource, MSG_RIGHT_SYMBOL, NEW_LINE));
            //切换到指定的数据源
            DataSourceContextHolder.setDataSource(dataSource);
            //调用TargetDataSource标记的切换数据源方法
            Object result = invocation.proceed();
            //移除当前线程对应的数据源
            DataSourceContextHolder.remove();
            LoggerUtil.info(invocation.getClass(), StringUtils.join(MSG_CONTROLLER, invocation.getThis().getClass(), ".", method.getName(), MSG_DATASOURCE_END, dataSource, MSG_RIGHT_SYMBOL, NEW_LINE));

            return result;
        } catch (Throwable e){
            //移除当前线程对应的数据源
            DataSourceContextHolder.remove();
            LoggerUtil.error(invocation.getClass(), StringUtils.join(MSG_CONTROLLER, invocation.getThis().getClass(), ".", method.getName(), MSG_DATASOURCE_END, dataSource, MSG_RIGHT_SYMBOL, NEW_LINE));
            throw new Throwable(e);
        }
    }

}

6.启用注解模式多数据源要在启动类上引入sqlSessionTemplate

@EnableTransactionManagement
@SpringBootApplication(scanBasePackages = {"com.yaomy.control"})
@MapperScan(basePackages = {"com.yaomy.control.*.mapper"}, sqlSessionTemplateRef = "sqlSessionTemplate")
public class HandlerBootStrap {
    public static void main(String[] args) {
        SpringApplication.run(HandlerBootStrap.class, args);
    }
}

7.使用示例

@Service
@Transactional
public class JobServiceImpl implements JobService {
   @Autowired
    private JobMapper jobMapper;
    @Override
    @TargetDataSource(DbType.DEFAULT_DATASOURCE)
    public Job findJob(String desc) {
        Job job1 = new Job();
        job1.setJobDesc("test测试12678");
       jobMapper.updateJob(job1);
        System.out.println("-------updateJob----------");
       Job job = jobMapper.findJob();

        System.out.println(job);
       return job;
    }
}

看下打印日志如下:

[2019-09-10 17:39:09.057] [000-exec-1] [INFO ] [c.y.c.t.serviceImpl.JobServiceImpl  :16  ] : 类|方法  :class com.yaomy.control.test.serviceImpl.JobServiceImpl.findJob开始执行,切换数据源到【spring】

[2019-09-10 17:39:09.250] [000-exec-1] [INFO ] [c.alibaba.druid.pool.DruidDataSource:1003] : {dataSource-1} inited
-------updateJob----------
com.yaomy.control.test.po.Job@68effeae
[2019-09-10 17:39:09.677] [000-exec-1] [INFO ] [.CglibAopProxy$CglibMethodInvocation:16  ] : 类|方法  :class com.yaomy.control.test.serviceImpl.JobServiceImpl.findJob执行结束,移除数据源【spring】

GitHub源码:https://github.com/mingyang66/spring-parent

已标记关键词 清除标记
项目描述 说明: spring security 全注解式的权限管理 动态配置权限,角色和资源,权限控制到按钮粒度 采用token进行权限校验,禁用session,未登录返回401,权限不足返回403 采用redis存储token及权限信息 内置功能: 用户管理:用户查询、添加用户、修改用户、给用户分配角色 菜单管理:菜单列表、添加菜单、修改菜单、删除菜单、权限配置、菜单图标设置、菜单排序 角色管理:角色查询、添加角色、修改角色、删除角色 代码生成:根据表名生成bean、controller、dao、Mapper.xml、列表页、搜索、分页、新增页、修改页 job集群:创建job、取消job、查询job、下拉搜索spring bean 数据源监控:druid 接口swagger文档 日志查询 邮件管理:发送邮件、搜索邮件 文件管理:上传文件、文件列表、文件删除 公告管理:公告未读提醒、发布公告、查询公告、公告阅读人列表 excel下载:自定义sql导出excel、也可在页面展示sql结果数据 字典管理:一些常量字典的维护 个人信息修改 修改密码 头像修改 其他说明: 日志模块 sl4j日志分包:将sql日志、业务日志、异常日志进行了分离,更方便定位问题 日志表:使用aop拦截实现 权限控制:基于token方式,禁用session 对各种不同异常进行了全局统一处理 使用lombok简化java代码,让源码更简洁,可读性高 mybatis未进行二次封装,原滋原味,简单sql采用注解,复杂sql采用Mapper.xml配置 使用了layui的弹出层、菜单、文件上传、富文本编辑、日历、选项卡、数据表格等 表单数据采用bootstrapValidator校验,简单快捷方便 运行环境 jdk8+mysql+redis+IntelliJ IDEA+maven 项目技术(必填) Springboot+Mybatis+ SpringMvc+springsecrity+Redis+bootstrap+jquery 数据库文件 压缩包内 jar包文件 maven搭建
相关推荐
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页