文档章节

spring boot 2.0.0.release 之 mybatis、druid动态数据源

路程
 路程
发布于 2018/05/17 18:26
字数 1278
阅读 585
收藏 0

pom.xml清单:

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>

   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
   </dependency>

   <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
   </dependency>

   <!-- 常用依赖库 -->
   <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.6</version>
   </dependency>

   <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>1.11</version>
   </dependency>

   <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-collections4</artifactId>
      <version>4.1</version>
   </dependency>

   <!-- mybaits -->
   <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.3.1</version>
   </dependency>

   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
   </dependency>

   <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
   <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.9</version>
   </dependency>


   <!--引入日志 @Slf4j注解-->
   <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
   </dependency>

</dependencies>

druid-spring-boot-starter 建议引用1.1.9 (目前最新版本),旧的版本可能抛出某些class找不到的异常;

1. 多数据源连接配置:

db.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
db.datasource.ds1.url=jdbc:mysql://host:port/table?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&generateSimpleParameterMetadata=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC
db.datasource.ds1.username=******
db.datasource.ds1.password=******

db.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
db.datasource.ds2.url=jdbc:mysql://host:port/table1?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&generateSimpleParameterMetadata=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC
db.datasource.ds2.username=******
db.datasource.ds2.password=******

2. 实现多数据源,我们使用数据源路由AbstractRoutingDataSource来实现,那就先了解一下它:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
public abstract class AbstractDataSource implements DataSource

AbstractRoutingDataSource 继承了AbstractDataSource ,AbstractDataSource 又实现了DataSource,所以它本身就是一个数据源类,那就看看它是怎么获取数据源的:

public Connection getConnection() throws SQLException {
    return this.determineTargetDataSource().getConnection();
}
protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = this.determineCurrentLookupKey();
    DataSource 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 + "]");
    } else {
        return dataSource;
    }
}

Object lookupKey = this.determineCurrentLookupKey();

DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);

通过这两行可以得知是通过determineCurrentLookupKey方法获取的数据源Key,然后根据key在resolvedDataSources中获取数据源对象,resolvedDataSources是一个Map对象;

public void afterPropertiesSet() {
    if(this.targetDataSources == null) {
        throw new IllegalArgumentException("Property \'targetDataSources\' is required");
    } else {
        this.resolvedDataSources = new HashMap(this.targetDataSources.size());
        this.targetDataSources.forEach((key, value) -> {
            Object lookupKey = this.resolveSpecifiedLookupKey(key);
            DataSource dataSource = this.resolveSpecifiedDataSource(value);
            this.resolvedDataSources.put(lookupKey, dataSource);
        });
        if(this.defaultTargetDataSource != null) {
            this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
        }

    }
}

这个函数是AbstractRoutingDataSource实现InitializingBean接口中的一个函数,可以看出resolvedDataSources的数据是来自targetDataSources这个属性的,然而targetDataSources 属性是可以赋值的:

public void setTargetDataSources(Map<Object, Object> targetDataSources) {
    this.targetDataSources = targetDataSources;
}

所以,我们的动态数据源就如下来实现:

  *1 ,创建一个动态数据源持有者,负责利用ThreadLocal存取数据源名称

public class DynamicDataSourceHolder {

    /**
     * 本地线程共享对象
     */
    private static ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();

    public static void setDataSource(String dataSourceName){
        THREAD_LOCAL.set(dataSourceName);
    }

    public static String getDataSource(){
        return THREAD_LOCAL.get();
    }

    public static void removeDataSource(){
        THREAD_LOCAL.remove();
    }

}

 *2,创建一个动态数据源实现类来继承AbstractRoutingDataSource类,重写其determineCurrentLookupKey方法,其返回的就是数据源的key

@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource{

    /**
     * 数据源路由,此方用于产生要选取的数据源逻辑名称
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        log.info("数据源为:"+DynamicDataSourceHolder.getDataSource());
        return DynamicDataSourceHolder.getDataSource();
    }

}

 *3,配置多数据源

public class DataSourceKey {

    public static final String DATA_SOURCE_1 = "DATA_SOURCE_1";
    public static final String DATA_SOURCE_2 = "DATA_SOURCE_2";

}
@Configuration
public class DataSourceConfig {

    //数据源1
    @Bean
    @ConfigurationProperties(prefix = "db.datasource.ds1")
    public DataSource ds1(){
        return DruidDataSourceBuilder.create().build();
    }

    //数据源2
    @Bean
    @ConfigurationProperties(prefix = "db.datasource.ds2")
    public DataSource ds2(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    public DataSource dataSource(){
        //按照目标数据源名称和目标数据源对象的映射存放在Map中
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceKey.DATA_SOURCE_1, ds1());
        targetDataSources.put(DataSourceKey.DATA_SOURCE_2, ds2());
        //采用是想AbstractRoutingDataSource的对象包装多数据源
        DynamicDataSource dataSource = new DynamicDataSource();
        //这里就是把多个数据源信息赋值给resolvedDataSources属性
        dataSource.setTargetDataSources(targetDataSources);
        //设置默认的数据源,当拿不到数据源时,使用此配置
        dataSource.setDefaultTargetDataSource(ds1());
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager txManager(){
        return new DataSourceTransactionManager(dataSource());
    }

}

 *4,使用aop切面技术切换数据源

创建一个注解类

/**
 * @desc 目标数据源注解,注解在方法上指定数据源的名称
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TargetDataSource {

    String value(); //此处接收的是数据源的名称

}

创建一个切面类

/**
 * @desc 数据源AOP切面定义
 */
@Aspect
@Component
@Slf4j
public class DataSourceAspect {

    /**
     * 切换放在mapper接口的方法上,所以这里要配置AOP切面的切入点
     */
    @Pointcut("execution(* com.sunflower.mapper.*.*.*(..))")
    public void dataSourcePointCut(){}

    /** 
      * 根据切点信息获取调用函数是否用TargetDataSource切面注解描述,如果设置了数据源,则进行数据源切换
      */
    @Before("dataSourcePointCut()")
    public void before(JoinPoint joinPoint){
        Object target = joinPoint.getTarget();
        String method = joinPoint.getSignature().getName();
        Class<?>[] clazz =  target.getClass().getInterfaces();
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
        try{
            Method m = clazz[0].getMethod(method,parameterTypes);
            if(null!=m && m.isAnnotationPresent(TargetDataSource.class)){
                TargetDataSource td = m.getAnnotation(TargetDataSource.class);
                String dataSourceName = td.value();
                DynamicDataSourceHolder.setDataSource(dataSourceName);
                log.info("current thread " + Thread.currentThread().getName() + " add " + dataSourceName + " to ThreadLocal, request method name is : "+method);
            }else{
                DynamicDataSourceHolder.setDataSource(DataSourceKey.DATA_SOURCE_1);
                log.info("use default datasource , request method name is : "+method);
            }
        }catch (Exception e){
            log.info("current thread " + Thread.currentThread().getName() + " add data to ThreadLocal error", e);
        }
    }


    /**
     * 执行完切面后,将线程共享中的数据源名称清空,数据源恢复为原来的默认数据源
     */
    @After("dataSourcePointCut()")
    public void after(JoinPoint joinPoint){
        DynamicDataSourceHolder.removeDataSource();
    }

}

 *5,使用

因为我是在Mapper处进行切入,所以在Mapper接口上进行数据源切换

@Component
public interface UserMapper {

    String getUserNameById();

    @TargetDataSource(DataSourceKey.DATA_SOURCE_2)
    String getBookNameById();

    String getUserEmailById();

}
@Service
public class UserService {

    @Autowired
    public UserMapper userMapper;

    public String getUserBook(){
        return userMapper.getUserNameById()+"--->"+userMapper.getBookNameById()+"--->"+userMapper.getUserEmailById();

    }

}

*6. 结果展示

o.a.c.c.C.[Tomcat].[localhost].[/life]   : Initializing Spring FrameworkServlet 'dispatcherServlet'
o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 38 ms
com.sunflower.utils.ds.DataSourceAspect  : use default datasource , request method name is : getUserNameById
c.sunflower.utils.ds.DynamicDataSource   : 数据源为:DATA_SOURCE_1
com.alibaba.druid.pool.DruidDataSource   : {dataSource-3} inited
com.sunflower.utils.ds.DataSourceAspect  : current thread http-nio-9527-exec-1 add DATA_SOURCE_2 to ThreadLocal, request method name is : getBookNameById
c.sunflower.utils.ds.DynamicDataSource   : 数据源为:DATA_SOURCE_2
com.alibaba.druid.pool.DruidDataSource   : {dataSource-4} inited
com.sunflower.utils.ds.DataSourceAspect  : use default datasource , request method name is : getUserEmailById
c.sunflower.utils.ds.DynamicDataSource   : 数据源为:DATA_SOURCE_1
jobs--->《平凡的世界》--->123@em.com

© 著作权归作者所有

路程

路程

粉丝 4
博文 10
码字总数 4054
作品 0
西安
程序员
私信 提问
苞米豆多数据源启动器 2.0.1 发布,Bug 修复版本

苞米豆多数据源启动器 2.0.1 发布了。强烈建议升级!更新内容: 修复一个方法缓存的bug,会引起同名方法的注解失效。 底层代码的重命名和部分格式的调整。 源码地址: https://gitee.com/ba...

小锅盖
2018/08/09
501
4
Spring Boot整合MyBatis学习总结

公司的很多项目都陆陆续续引入了Spring Boot,通过对Spring Boot的接触了解发现其真的是大大地简化了开发、简化了依赖配置,很多功能注解一下就可以实现,真的是太方便了。下面记录了一个Spr...

zhuwensheng
2018/06/29
0
0
SpringBoot整合Mybatis+Druid

1.SpringBoot 作为一款约定大于配置的微服务框架,得到了业界的大量推广和应用。对SpringBoot的学习使用可以有效的帮助开发简化开发流程,配置过程,部署过程。 2.数据库的交互是开发过程中很...

扁桃体准备发言了
2018/08/16
0
0
Spring Boot 集成 Mybatis 实现双数据源

这里用到了Spring Boot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源实现数据库的读写分离。 添加依赖 加入Mybatis启动器,这里添加了Druid连接池、Oracle数据库驱动...

Java技术栈
2018/06/08
0
0
springboot集成mybatis

springboot集成mybatis application.yml 从哪里找到这些配置项: springboot 自己的配置项 https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties......

黄威
2018/07/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

idea下springboot 项目在static目录下添加文件不生效

idea下springboot 项目在static目录下添加文件不生效 问题描述 是这样子的,我的项目目录结构如下: 我在static目录下,创建了index.html和aaaa.jpg这两个文件。然后,启动服务访问 http://l...

wotrd
昨天
5
0
k8s1.14 一、环境

1. 4台虚拟机 (CentOS Linux release 7.2.1511 (Core) ) 192.168.130.211 master 192.168.130.212 node1 192.168.130.213 node2 192.168.130.214 node3 2. 设置服务器hostname 2.1 设置本机......

ThomasCheng
昨天
3
0
盖茨:如果我现在开创一家公司 将会专注于AI

新浪科技讯,北京时间 6 月 26 日凌晨消息,微软联合创始人比尔·盖茨(Bill Gates)在周一接受采访时表示,如果他今天从哈佛大学辍学并开创一家新公司,那么这家公司将会专注于人工智能(A...

linuxCool
昨天
1
0
聊聊feign的Retryer

序 本文主要研究一下feign的Retryer Retryer feign-core-10.2.3-sources.jar!/feign/Retryer.java public interface Retryer extends Cloneable { /** * if retry is permitted, retur......

go4it
昨天
12
0
HyperLogLog简介

  (1)HyperLogLog简介      在Redis 在 2.8.9 版本才添加了 HyperLogLog,HyperLogLog算法是用于基数统计的算法,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个...

SEOwhywhy
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部