专栏首页架构探险之道[Spring Boot] Spring Boot 多数据源动态切换[自定义注解&AOP]

[Spring Boot] Spring Boot 多数据源动态切换[自定义注解&AOP]

[Spring Boot] Spring Boot 多数据源动态切换[自定义注解&AOP]

@TOC

手机用户请横屏获取最佳阅读体验,REFERENCES中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。

平台

地址

CSDN

https://blog.csdn.net/sinat_28690417

简书

https://www.jianshu.com/u/3032cc862300

个人博客

http://xiazhaoyang.tech/

开发环境描述

------------------------------------------------------------
Gradle 4.7
------------------------------------------------------------

Build time:   2018-04-18 09:09:12 UTC
Revision:     b9a962bf70638332300e7f810689cb2febbd4a6c

Groovy:       2.4.12
Ant:          Apache Ant(TM) version 1.9.9 compiled on February 2 2017
JVM:          1.8.0_171 (Oracle Corporation 25.171-b11)
OS:           Mac OS X 10.13.5 x86_64

依赖描述

  • spring-boot-starter-web:2.1.0.RELEASE
  • mybatis-spring-boot-starter:1.3.2
  • aspectjrt:1.9.2
  • aspectjweaver:1.9.2
  • mysql-connector-java:8.0.13

基础框架准备

  • 准备两个数据源(mysql中schema和database是一个意思)并生成对应MapperModel CREATE SCHEMA db_capsule; CREATE SCHEMA db_flowable; 分别在这两个数据库中创建一张表:db_capsule.tb_common_user_infodb_flowable.tb_common_account_info
  create table tb_common_user_info
  (
    user_id     bigint auto_increment
    comment '人员ID'
      primary key,
    age         int                                not null
    comment '年龄',
    name        varchar(128)                       not null
    comment '姓名',
    email       varchar(128)                       not null
    comment '邮箱',
    remark      varchar(1024)                      null
    comment '备注',
    is_delete   int default '0'                    null
    comment '表明数据是否已删除 0-未删除,1-已删除',
    create_time datetime default CURRENT_TIMESTAMP not null
    comment '创建时间'
  )
    comment '通用人员信息表';

  create table tb_common_account_info
  (
    account_id   bigint auto_increment
    comment '账号ID'
      primary key,
    account_name varchar(128)                       not null
    comment '登录名',
    user_id      bigint                             null
    comment '关联用户ID',
    remark       varchar(1024)                      null
    comment '备注',
    is_delete    int default '0'                    null
    comment '表明数据是否已删除 0-未删除,1-已删除',
    create_time  datetime default CURRENT_TIMESTAMP not null
    comment '创建时间'
  )
    comment '账号信息表';

生成基础Mapper(代码生成插件即可)

  • yaml中配置默认数据源和自定义数据源
spring:
  mvc:
   static-path-pattern: /**
  resources:
    static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/static/flowable-modeler
  # 默认主数据源
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    username: xx
    password: xxxx
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://x.x.x.x:3306/db_flowable?useUnicode=true&characterEncoding=utf-8
  jpa:
    hibernate:
      ddl-auto: update
    database: MYSQL

# 自定义数据源
custom:
  datasource:
    - key: capsule
      type: com.alibaba.druid.pool.DruidDataSource
      username: xxx
      password: xxxx
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://x.x.x.x:3306/db_capsule?useUnicode=true&characterEncoding=utf-8
  • 启动类开启AOP
package com.example;

import com.example.common.config.database.DynamicDataSourceRegister;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * <p>
 *  - SpringBootApplication 启动类
 *  - ComponentScan 实例扫描
 *  - MapperScan Mybatis Dao 扫描
 *  - EnableTransactionManagement 开启事务
 *  - Import 启动前注入实例,动态切换数据源
 * </p>
 *
 * @author xiazhaoyang
 * @version v1.0.0
 * @date 2019/3/14 23:08
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人} 2019/3/14
 * @modify reason: {方法名}:{原因}
 * ...
 */
@SpringBootApplication
@ComponentScan(basePackages = {"com.example"})
@Import({DynamicDataSourceRegister.class})
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class CapsuleFlowableApplication {

    public static void main(String[] args) {
        SpringApplication.run(CapsuleFlowableApplication.class, args);
    }
}
  • 构建Service层测试代码 AccountInfoServiceImpl
package com.example.service.common.impl;
import com.example.common.base.BaseServiceImpl;
import com.example.common.base.RootMapper;
import com.example.common.config.database.TargetDataSource;
import com.example.core.common.dao.AccountInfoMapper;
import com.example.core.common.model.AccountInfo;
import com.example.core.common.model.AccountInfoExample;
import com.example.service.common.AccountInfoService;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/03/24
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人}  2019/03/24
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Slf4j
@Service
public class AccountInfoServiceImpl extends BaseServiceImpl<AccountInfo,AccountInfoExample> implements AccountInfoService {

    @Resource
    private AccountInfoMapper accountInfoMapper;

    @Override
    public RootMapper<AccountInfo, AccountInfoExample> getMapper() {
        return accountInfoMapper;
    }

    @Override
    @TargetDataSource(name="dataSource")//此处为切换数据源的注解
    public List<AccountInfo> selectList() {
        return selectByExample(new AccountInfoExample());
    }
}

UserInfoServiceImpl

package com.example.service.common.impl;
import com.example.common.config.database.TargetDataSource;
import com.example.core.common.dao.UserInfoMapper;
import com.example.core.common.model.UserInfo;
import com.example.core.common.model.UserInfoExample;
import com.example.service.common.UserInfoService;
import com.example.common.base.BaseServiceImpl;
import com.example.common.base.RootMapper;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/03/24
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人}  2019/03/24
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Slf4j
@Service
public class UserInfoServiceImpl extends BaseServiceImpl<UserInfo,UserInfoExample> implements UserInfoService {

    @Resource
    private UserInfoMapper userInfoMapper;

    @Override
    public RootMapper<UserInfo, UserInfoExample> getMapper() {
        return userInfoMapper;
    }

    @Override
    @TargetDataSource(name="capsule")
    public List<UserInfo> selectList() {
        return selectByExample(new UserInfoExample());
    }

    @Override
    @TargetDataSource(name="capsule")//此处为切换数据源的注解
    public int insert(UserInfo userInfo) {
        return super.insert(userInfo);
    }
}
  • 构建Controller层测试代码

AccountInfoController

package com.example.web.controller.common;

import com.example.common.base.BaseController;
import com.example.common.base.ResponseJson;
import com.example.core.common.model.AccountInfo;
import com.example.service.common.AccountInfoService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Date;

/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/03/24
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人}  2019/03/24
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Slf4j
@RestController
@RequestMapping("/common/accountInfo/")
public class AccountInfoController extends BaseController<AccountInfo>{

    @Resource
    AccountInfoService accountInfoService;

    @Override
    @RequestMapping(value="addition", method = RequestMethod.POST)
    public ResponseJson add(@RequestBody AccountInfo accountInfo) {
        accountInfoService.insert(AccountInfo.builder()
                .accountId(1L)
                .accountName("admin")
                .createTime(new Date())
                .userId(1L)
                .remark("ADMIN REMARK")
                .isDelete(0)
                .build());
        return new ResponseJson();
    }

    @Override
    @RequestMapping(value="deletion", method = RequestMethod.POST)
    public ResponseJson delete(@RequestParam String id) {
        return new ResponseJson();
    }

    @Override
    @RequestMapping(value="update", method = RequestMethod.POST)
    public ResponseJson update(@RequestBody AccountInfo accountInfo) {
        return new ResponseJson();
    }

    @Override
    @RequestMapping(value="detail", method = RequestMethod.POST)
    public ResponseJson detail(@RequestParam String id) {
        return ResponseJson.OK(accountInfoService.selectList());
    }
}

UserInfoController

package com.example.web.controller.common;
import com.example.core.common.model.AccountInfo;
import com.example.core.common.model.UserInfo;
import com.example.service.common.UserInfoService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import com.example.common.base.BaseController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import com.example.common.base.ResponseJson;

import java.util.Date;

/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0.0
 * @date 2019/03/24
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人}  2019/03/24
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Slf4j
@RestController
@RequestMapping("/common/userInfo/")
public class UserInfoController extends BaseController<UserInfo>{

    @Resource
    UserInfoService userInfoService;

    @Override
    @RequestMapping(value="addition", method = RequestMethod.POST)
    public ResponseJson add(@RequestBody UserInfo userInfo) {
        userInfoService.insert(UserInfo.builder()
                .userId(1L)
                .age(11)
                .createTime(new Date())
                .email("xxx@xx.com.cn")
                .name("Yiyuery")
                .remark("xxx")
                .build());
        return new ResponseJson();
    }

    @Override
    @RequestMapping(value="deletion", method = RequestMethod.POST)
    public ResponseJson delete(@RequestParam String id) {
        return new ResponseJson();
    }

    @Override
    @RequestMapping(value="update", method = RequestMethod.POST)
    public ResponseJson update(@RequestBody UserInfo userInfo) {
        return new ResponseJson();
    }

    @Override
    @RequestMapping(value="detail", method = RequestMethod.POST)
    public ResponseJson detail(@RequestParam String id) {
        return ResponseJson.OK(userInfoService.selectList());
    }
}

动态数据源注册

  • 注解定义
  /*
   * @ProjectName: 编程学习
   * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
   * @address:     http://xiazhaoyang.tech
   * @date:        2019/3/23 17:05
   * @email:       xiazhaoyang@live.com
   * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
   */
  package com.example.common.config.database;

  import java.lang.annotation.*;

  /**
   * <p>
   *  在方法上使用,用于指定使用哪个数据源
   * </p>
   *
   * @author xiazhaoyang
   * @version v1.0.0
   * @date 2019/3/23 17:05
   * @modificationHistory=========================逻辑或功能性重大变更记录
   * @modify By: {修改人} 2019/3/23
   * @modify reason: {方法名}:{原因}
   * ...
   */
  @Target({ ElementType.METHOD, ElementType.TYPE })
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  public @interface TargetDataSource {
      String name();
  }
  • DynamicDataSourceRegister数据源实例bean注册
  /*
   * @ProjectName: 编程学习
   * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
   * @address:     http://xiazhaoyang.tech
   * @date:        2019/3/23 17:04
   * @email:       xiazhaoyang@live.com
   * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
   */
  package com.example.common.config.database;

  import lombok.extern.slf4j.Slf4j;
  import org.apache.commons.lang3.StringUtils;
  import org.springframework.beans.MutablePropertyValues;
  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  import org.springframework.beans.factory.support.GenericBeanDefinition;
  import org.springframework.boot.context.properties.bind.Bindable;
  import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
  import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
  import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
  import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
  import org.springframework.boot.jdbc.DataSourceBuilder;
  import org.springframework.context.EnvironmentAware;
  import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
  import org.springframework.core.env.Environment;
  import org.springframework.core.type.AnnotationMetadata;
  import org.springframework.boot.context.properties.bind.Binder;


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

  import static com.example.common.util.CapsuleStringUtil.requireNotSatisfy;

  /**
   * <p>
   *
   * </p>
   *
   * @author xiazhaoyang
   * @version v1.0.0
   * @date 2019/3/23 17:04
   * @modificationHistory=========================逻辑或功能性重大变更记录
   * @modify By: {修改人} 2019/3/23
   * @modify reason: {方法名}:{原因}
   * ...
   */
  @Slf4j
  public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

      /**
       * 参数绑定工具
       */
      private Binder binder;
      /**
       * 如配置文件中未指定数据源类型,使用该默认值
       */
      private static final Object DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";

      /**
       * 默认数据源
       */
      private DataSource defaultDataSource;
      /**
       * 自定义数据源
       */
      private Map<String, DataSource> customDataSources = new HashMap<>();
      /**
       * 数据源参数配置别名
       */
      private final static ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases(); //别名
      /**
       * 配置上下文(也可以理解为配置文件的获取工具)
       */
      private Environment env;


      static {
          //由于部分数据源配置不同,所以在此处添加别名,避免切换数据源出现某些参数无法注入的情况
          aliases.addAliases("url", "jdbc-url");
          aliases.addAliases("username", "user");
      }

      @Override
      public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
          Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
          // 将主数据源添加到更多数据源中
          targetDataSources.put("dataSource", defaultDataSource);
          DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
          // 添加更多数据源
          targetDataSources.putAll(customDataSources);
          DynamicDataSourceContextHolder.dataSourceIds.addAll(customDataSources.keySet());
          // 创建DynamicDataSource
          GenericBeanDefinition define = new GenericBeanDefinition(); //bean定义类
          define.setBeanClass(DynamicDataSource.class); //设置bean的类型,此处DynamicDataSource是继承AbstractRoutingDataSource的实现类
          MutablePropertyValues mpv = define.getPropertyValues(); //需要注入的参数,类似spring配置文件中的<property/>
          mpv.add("defaultTargetDataSource", defaultDataSource); //添加默认数据源,避免key不存在的情况没有数据源可用
          mpv.add("targetDataSources", targetDataSources); //添加其他数据源
          registry.registerBeanDefinition("datasource", define); //将该bean注册为datasource,不使用spring-boot自动生成的datasource
          log.info("Dynamic DataSource Registry");
      }

      /**
       * 创建DataSource
       *
       * @return
       * @author SHANHY
       * @create 2016年1月24日
       */
      private DataSource buildDataSource(Map<String, Object> dsMap) {
          try {
              Object type = dsMap.get("type");
              if (type == null)
                  type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource

              Class<? extends DataSource> dataSourceType;
              dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
              String driverClassName = dsMap.get("driver-class-name").toString();
              String url = dsMap.get("url").toString();
              String username = dsMap.get("username").toString();
              String password = dsMap.get("password").toString();
              DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
                      .username(username).password(password).type(dataSourceType);
              return factory.build();
          } catch (Throwable e) {
              log.error("buildDataSource failed!",e);
          }
          return null;
      }

      /**
       * 加载多数据源配置
       */
      @Override
      public void setEnvironment(Environment environment) {
          this.env = environment;
          binder = Binder.get(env); //绑定配置器
          initDefaultDataSource();
          initCustomDataSources();
      }

      /**
       * 初始化主数据源
       *
       * @author SHANHY
       * @create 2016年1月24日
       */
      private void initDefaultDataSource() {
          //读取数据源参数配置
          Map props = binder.bind("spring.datasource", Map.class).get();
          Map<String, Object> dsMap = new HashMap<>();
          dsMap.put("type", props.get("type"));
          dsMap.put("driver-class-name", props.get("driver-class-name"));
          dsMap.put("url", props.get("url"));
          dsMap.put("username", props.get("username"));
          dsMap.put("password", props.get("password"));
          defaultDataSource = buildDataSource(dsMap);
          dataBinder(defaultDataSource, props);
      }

      /**
       * 初始化更多数据源
       *
       * @author SHANHY
       * @create 2016年1月24日
       */
      private void initCustomDataSources() {
          // 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
          List<Map> configs = binder.bind("custom.datasource", Bindable.listOf(Map.class)).get();
          String dsPrefix;
          DataSource custom;
          for (Map config : configs) {
              dsPrefix = requireNotSatisfy(p -> StringUtils.isNotEmpty(config.get("key").toString()), config.get("key").toString(), "default");
              custom = buildDataSource(config);
              customDataSources.put(dsPrefix, custom);
              dataBinder(custom, config);
          }
      }

      /**
       * 绑定参数,以下三个方法都是参考DataSourceBuilder的bind方法实现的,
       * 目的是尽量保证我们自己添加的数据源构造过程与spring-boot保持一致
       *
       * @param dataSource
       * @param properties
       */
      private void dataBinder(DataSource dataSource, Map properties) {
          ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
          Binder binderEx = new Binder(source.withAliases(aliases));
          binderEx.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(dataSource));  //将参数绑定到对象
      }

  }
  • DynamicDataSourceContextHolder缓存当前线程上下文中数据源标识ID
/*
 * @ProjectName: 编程学习
 * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
 * @address:     http://xiazhaoyang.tech
 * @date:        2019/3/23 16:57
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.example.common.config.database;

import com.example.common.util.CapsuleStringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;

import static com.example.common.util.CapsuleStringUtil.requireNotSatisfyThrowException;

/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version v1.0.0
 * @date 2019/3/23 16:57
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人} 2019/3/23
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Slf4j
class DynamicDataSourceContextHolder {

    //保存当前线程的数据源对应的key
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    static List<String> dataSourceIds = new ArrayList<>();

    static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    static String getDataSourceType() {
        try {
            return requireNotSatisfyThrowException(p->StringUtils.isNotBlank(contextHolder.get()),contextHolder.get(),"can not found datasource by key: '%s',this session may use default datasource","");
        } catch (NullPointerException e) {
            contextHolder.set("dataSource");
            log.error(e.getMessage());
            //如果动态数据源获取为空,返回默认数据源
            return contextHolder.get();
        }
    }

    static void clearDataSourceType() {
        contextHolder.remove();
    }

    /**
     *
     * 判断指定DataSrouce当前是否存在
     * @return
     * @author xiazhaoyang
     * @date 2019/3/24 17:52
     * @modify by: {修改人} 2019/3/24 17:52
     * @modify by reason:
     * @since 1.0.0
     */
    static boolean containsDataSource(String dataSourceId){
        return dataSourceIds.contains(dataSourceId);
    }
}
  • DataSourceDynamicAspectAOP切片解析注解
  /*
   * @ProjectName: 编程学习
   * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
   * @address:     http://xiazhaoyang.tech
   * @date:        2019/3/24 11:03
   * @email:       xiazhaoyang@live.com
   * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
   */
  package com.example.common.config.database;

  import org.aspectj.lang.annotation.Aspect;
  import org.springframework.core.annotation.Order;
  import lombok.extern.slf4j.Slf4j;
  import org.aspectj.lang.annotation.*;
  import org.springframework.stereotype.Component;
  import org.aspectj.lang.JoinPoint;

  /**
   * <p>
   * 声明数据源切面
   * </p>
   *
   * @author xiazhaoyang
   * @version v1.0.0
   * @date 2019/3/24 11:03
   * @modificationHistory=========================逻辑或功能性重大变更记录
   * @modify By: {修改人} 2019/3/24
   * @modify reason: {方法名}:{原因}
   * ...
   */
  @Component
  @Aspect
  @Order(-10) //使该切面在事务之前执行
  @Slf4j
  public class DataSourceDynamicAspect {

      /**
       * AOP切面拦截注解 TargetDataSource 先从当前线程中取出数据库标识
       * @param point
       * @param ds
       */
      @Before("@annotation(ds)")
      public void changeDataSource(JoinPoint point, TargetDataSource ds) {
          String dsId = ds.name();
          if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
              log.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());
          } else {
              log.debug("Use DataSource : {} > {}", ds.name(), point.getSignature());
              DynamicDataSourceContextHolder.setDataSourceType(ds.name());
          }
      }

      /**
       * AOP切面拦截注解 TargetDataSource 从当前线程中删除数据库标识
       * @param point
       * @param ds
       */
      @After("@annotation(ds)")
      public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
          log.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
          DynamicDataSourceContextHolder.clearDataSourceType();
      }


  }
  • DynamicDataSource动态数据源对象定义,用于数据源实例注册到Spring实例工厂后的路由
/*
 * @ProjectName: 编程学习
 * @Copyright:   2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
 * @address:     http://xiazhaoyang.tech
 * @date:        2019/3/23 16:56
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.example.common.config.database;

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

/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version v1.0.0
 * @date 2019/3/23 16:56
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人} 2019/3/23
 * @modify reason: {方法名}:{原因}
 * ...
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * AbstractRoutingDataSource抽象类实现方法,即获取当前线程数据源的key
     *
     * @return data unique key
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

AbstractRoutingDataSource

Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()}
calls to one of various target DataSources based on a lookup key. The latter is usually
(but not necessarily) determined through some thread-bound transaction context.
基于查找标识键来调用各种目标数据源之一的路由 {@link #getConnection ()} 的抽象实现{@link javax.sql.DataSource}。后者通常是(但不一定) 通过某些线程绑定事务上下文确定。

效果

  • 账号信息查询
  • 人员信息查询

REFRENCES

  • Spring Boot 动态数据源(多数据源自动切换)
  • springboot2动态数据源的绑定
  • Spring Boot AOP 不生效排查
  • Spring Boot 日志配置(超详细)
  • SpringBoot根据包名进行区分使用多数据源
  • Spring 中基于 AOP 的 @AspectJ

本文分享自微信公众号 - 架构探险之道(zacsnz1314)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-03-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 大数据之脚踏实地学10--Hive独立式安装

    在《大数据之脚踏实地学09--Hive嵌入式安装》一文中我们详细介绍了Hive嵌入式的安装流程,即默认使用Hive的内置Derby数据库作为元信息的存储。但这样...

    1480
  • centos7下安装docker 以及简单使用

    yum install -y docker 备注:-y 表示不询问 使用默认配置进行安装,等待提示 完毕!

    庞小明
  • Centos6 安装 mysql 5.7.x系列

    Devops海洋的渔夫
  • SpringBoot+MyBatis+MySQL读写分离实战

    1. 引言 读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,无非两个,要么中间件帮我们做,要么程序自己做。因此,一般...

    java思维导图
  • 网站空间的类型

    网站空间就是指用来存储网站的文字、文档、数据库、图片等的空间。在大连网站建设完成后,网站的持有者需要购买空间才能发布内容。网站是否能够让用户拥有良好的体验以及是...

    大金SEO
  • 为什么要用自增主键?

    这固然没错,但是不那么具有说服力。最近在做商业账号的项目的时候,对这点体会尤为深刻。我觉得设置自增主键的最主要目的是:应对变化。

    Leetcode名企之路
  • python数据库-MySQL与python的交互(52)

    2.创建testMySQL.py模块对我们创建的MySQLManager.py模块测试

    Se7eN_HOU
  • 一日一技:在Python里面实现链式调用

    在这种写法里面,query对象有一个 filter方法,这个方法的返回数据还可以继续调用 filter方法,可以这样无限制地调用下去。

    青南
  • 分布式架构之「 两阶段提交协议」

    两阶段提交协议是一种经典的强一致性中心化副本控制协议。虽然在工程中该协议有较多的问题,但研究该协议能很好的理解分布式系统的几个典型问题。

    猿哥
  • 13_开发品牌名称获取接口的基于本地缓存的fallback降级机制

    fallback,你之前都是必须去调用外部的依赖接口,或者从MySQL中去查询数据的,但是为了避免说可能外部依赖会有故障

    JavaEdge

扫码关注云+社区

领取腾讯云代金券