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

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

目录

  • [Spring Boot] Spring Boot 多数据源动态切换[自定义注解&AOP]
    • 基础框架准备
    • 动态数据源注册
    • 效果
    • REFRENCES
    • 更多

手机用户请横屏获取最佳阅读体验,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(代码生成插件即可)

[Spring Boot] Spring Boot 多数据源动态切换[自定义注解&AOP]_第1张图片

[Spring Boot] Spring Boot 多数据源动态切换[自定义注解&AOP]_第2张图片

  • 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;

/**
 * 

* - SpringBootApplication 启动类 * - ComponentScan 实例扫描 * - MapperScan Mybatis Dao 扫描 * - EnableTransactionManagement 开启事务 * - Import 启动前注入实例,动态切换数据源 *

* * @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;

/**
 * 

* *

* * @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;

/**
 * 

* *

* * @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;

/**
 * 

* *

* * @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;

/**
 * 

* *

* * @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("[email protected]") .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:       [email protected]
   * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
   */
  package com.example.common.config.database;
  
  import java.lang.annotation.*;
  
  /**
   * 

* 在方法上使用,用于指定使用哪个数据源 *

* * @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:       [email protected]
   * @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;
  
  /**
   * 

* *

* * @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配置文件中的 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:       [email protected]
 * @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;

/**
 * 

* *

* * @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:       [email protected]
   * @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;
  
  /**
   * 

* 声明数据源切面 *

* * @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:       [email protected]
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.example.common.config.database;

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

/**
 * 

* *

* * @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}。后者通常是(但不一定) 通过某些线程绑定事务上下文确定。

效果

  • 账号信息查询

[Spring Boot] Spring Boot 多数据源动态切换[自定义注解&AOP]_第3张图片

  • 人员信息查询

    [Spring Boot] Spring Boot 多数据源动态切换[自定义注解&AOP]_第4张图片

REFRENCES

  • Spring Boot 动态数据源(多数据源自动切换)

  • springboot2动态数据源的绑定

  • Spring Boot AOP 不生效排查

  • Spring Boot 日志配置(超详细)

  • SpringBoot根据包名进行区分使用多数据源

  • Spring 中基于 AOP 的 @AspectJ

更多

扫码关注“架构探险之道”,获取更多源码和文章资源

[Spring Boot] Spring Boot 多数据源动态切换[自定义注解&AOP]_第5张图片

知识星球(扫码加入获取源码和文章资源链接)

[Spring Boot] Spring Boot 多数据源动态切换[自定义注解&AOP]_第6张图片

你可能感兴趣的:(Spring,Boot,Spring,Spring,Boot)