前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Spring Boot] Spring Boot 多数据源动态切换[自定义注解&AOP]

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

作者头像
架构探险之道
发布2019-07-25 17:13:54
2.6K0
发布2019-07-25 17:13:54
举报
文章被收录于专栏:架构探险之道

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

@TOC

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

平台

地址

CSDN

https://blog.csdn.net/sinat_28690417

简书

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

个人博客

http://xiazhaoyang.tech/

开发环境描述

代码语言:javascript
复制
------------------------------------------------------------
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
代码语言:javascript
复制
  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中配置默认数据源和自定义数据源
代码语言:javascript
复制
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
代码语言:javascript
复制
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
代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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());
    }
}

动态数据源注册

  • 注解定义
代码语言:javascript
复制
  /*
   * @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注册
代码语言:javascript
复制
  /*
   * @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
代码语言:javascript
复制
/*
 * @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切片解析注解
代码语言:javascript
复制
  /*
   * @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实例工厂后的路由
代码语言:javascript
复制
/*
 * @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

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-03-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构探险之道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • [Spring Boot] Spring Boot 多数据源动态切换[自定义注解&AOP]
    • 基础框架准备
      • 动态数据源注册
        • 效果
          • REFRENCES
          相关产品与服务
          云数据库 SQL Server
          腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档