标题业务场景:大量的项目的数据库需要从db2迁移到oracle,为项目上线后出现不可预料的错误可及时回退,需要可以随时切换数据源

以控制层为切点,动态切换数据源,通过查询数据库查询当前应用的开关,来判断切换到哪一个数据源
考虑到db2和oracle的sql语句有一定差别,所以有些方法需要重写
然后通过注解切面,重新注入bean

此类用于从容器中获取bean

@Component
public final class SpringUtils implements ApplicationContextAware {@Getterprivate static ApplicationContext applicationContext;private static void setStaticApplicationContext(ApplicationContext applicationContext) {if (SpringUtils.applicationContext == null) {SpringUtils.applicationContext = applicationContext;}}public static <T> T getBean(Class<T> clazz) {return SpringUtils.applicationContext.getBean(clazz);}public static Object getBean(String name) {return SpringUtils.applicationContext.getBean(name);}public static String getProperty(String key) {return SpringUtils.applicationContext.getEnvironment().getProperty(key);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {setStaticApplicationContext(applicationContext);}
}

此类用于操作动态数据源

@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {private static final ThreadLocal<String> dataSourceKey = ThreadLocal.withInitial(() -> "defaultDataSource");private static final Map<Object, Object> dataSourcesMap = new ConcurrentHashMap<>(16);//选取当前数据源,除默认数据源外,key可以是数据库名+数据库类型的组合public static void setCurrentDataSource(String dataSourceName) {DynamicDataSource.dataSourceKey.set(dataSourceName);DynamicDataSource dynamicDataSource = (DynamicDataSource) SpringUtils.getBean("dynamicDataSource");dynamicDataSource.afterPropertiesSet();}//初始化用户自定义数据源,key可以是数据库名+数据库类型的组合public static void initCustomDataSource(String dataSourceName, HikariDataSource druidDataSource) {dataSourcesMap.put(dataSourceName, druidDataSource);}public static void closeAllDataSource() {for (Map.Entry<Object, Object> dataSourceEntry : dataSourcesMap.entrySet()) {try {((HikariDataSource) dataSourceEntry.getValue()).close();} catch (Exception e) {log.error("关闭数据源异常!");}}dataSourcesMap.clear();}public static String getDataSource() {return DynamicDataSource.dataSourceKey.get();}public static void clear() {DynamicDataSource.dataSourceKey.remove();}public static Map<Object, Object> getDataSourcesMap() {return dataSourcesMap;}@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSource.dataSourceKey.get();}
}

此类为动态数据源配置类

@Slf4j
@Configuration
@MapperScan(basePackages = "com.repository.db2", sqlSessionFactoryRef = "dynamicSessionFactory")
public class DynamicDataSourceConfig {@Value("${spring.datasource.druid.db02.driver-class-name}")private String driverClassName;@Value("${spring.datasource.druid.db02.url}")private String url;@Value("${spring.datasource.druid.db02.username}")private String username;@Value("${spring.datasource.druid.db02.password}")private String password;@Value("${spring.datasource.hikari.minimum-idle:1}")private int minimumIdle;@Value("${spring.datasource.hikari.maximum-pool-size:5}")private int maximumPoolSize;@Value("${spring.datasource.hikari.idle-timeout:30000}")private int idleTimeout;@Value("${spring.datasource.hikari.max-lifetime:0}")private int maxLifetime;@Value("${spring.datasource.hikari.connection-timeout:3000}")private int connectionTimeout;@Value("${spring.datasource.hikari.validation-timeout:5000}")private int validationTimeout;@Value("${spring.datasource.druid.db02.validation-query}")private String testQuery;@Bean("initDataSource")public DataSource dataSource() {HikariDataSource hikariDataSource = new HikariDataSource();hikariDataSource.setDriverClassName(driverClassName);hikariDataSource.setJdbcUrl(url);hikariDataSource.setUsername(username);hikariDataSource.setPassword(password);hikariDataSource.setMinimumIdle(minimumIdle);hikariDataSource.setMaxLifetime(maxLifetime);hikariDataSource.setMaximumPoolSize(maximumPoolSize);hikariDataSource.setIdleTimeout(idleTimeout);hikariDataSource.setConnectionTimeout(connectionTimeout);hikariDataSource.setValidationTimeout(validationTimeout);hikariDataSource.setConnectionTestQuery(testQuery);return hikariDataSource;}@Bean@DependsOn({"springUtils", "initDataSource"})public DynamicDataSource dynamicDataSource() {DataSource dataSource = (DataSource) SpringUtils.getBean("initDataSource");DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setDefaultTargetDataSource(dataSource);dynamicDataSource.setTargetDataSources(DynamicDataSource.getDataSourcesMap());return dynamicDataSource;}@Bean("dynamicSessionFactory")@DependsOn({"dynamicDataSource"})public SqlSessionFactory dynamicSessionFactory() throws Exception {DataSource dynamicDataSource = (DataSource) SpringUtils.getBean("dynamicDataSource");MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean();sessionFactoryBean.setDataSource(dynamicDataSource);MybatisConfiguration configuration = new MybatisConfiguration();configuration.setMapUnderscoreToCamelCase(true);configuration.setCallSettersOnNulls(true);sessionFactoryBean.setConfiguration(configuration);return sessionFactoryBean.getObject();}@Bean("dynamicTxManager")public DataSourceTransactionManager dynamicTxManager(@Qualifier("dynamicDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}

此类为数据源切换切面类

@Slf4j
@Aspect
@Component
public class ChangeDataAspect extends ApplicationObjectSupport {@Pointcut("execution(* com.web.controller..*.*(..)) &&" +"!execution(* com.web.controller.HealthController.*(..))")public void changeAspect() {};@Autowiredprivate ComParmInfMapper comParmInfMapper;@Around("changeAspect()")public Object changeDataSource(ProceedingJoinPoint jp) throws Throwable {try {if (getCurrentDataSource()) {DataSourceChangeHandle.lockDataSourceLock();DynamicDataSource.setCurrentDataSource("ORACLE");} else {DataSourceChangeHandle.lockDataSourceLock();DynamicDataSource.setCurrentDataSource("DB2");}log.info("DataSource Change Success ; Current DataSource: {}", DynamicDataSource.getDataSource());Object proceed = jp.proceed();return proceed;} catch (Throwable e) {log.error("DataSource Change Fail : {}", e.getMessage());throw e;} finally {DynamicDataSource.clear();DataSourceChangeHandle.getDataSourceLock().unlock();log.info("DataSource Clear AND Unlock");}}/*** @description true 切换oracle    false 切换db2* @author LGQ* @date 2022/5/11 9:49*/private Boolean getCurrentDataSource() {LambdaQueryWrapper<ComParmInfEntity> wrapper = new LambdaQueryWrapper<>();wrapper.eq(ComParmInfEntity::getParmTyp, "S901");wrapper.eq(ComParmInfEntity::getParmKey, "SW_MC_AP_TMS");ComParmInfEntity comParmInfEntity = comParmInfMapper.selectOne(wrapper);String parmVal = (null == comParmInfEntity || null == comParmInfEntity.getParmVal()) ? "" : comParmInfEntity.getParmVal().trim();//如果应用开关为空则获取总开关值if (StringUtils.isEmpty(parmVal)) {wrapper.clear();wrapper.eq(ComParmInfEntity::getParmTyp, "S901");wrapper.eq(ComParmInfEntity::getParmKey, "SWITCH_TO_ORACLE");comParmInfEntity = comParmInfMapper.selectOne(wrapper);parmVal = (null == comParmInfEntity || null == comParmInfEntity.getParmVal()) ? "" : comParmInfEntity.getParmVal().trim();//总开关为空则该应用暂未配置开关值,默认切量状态未 不切量(DB2数据库)if (StringUtils.isEmpty(parmVal)) {return false;}}return "Y".equals(parmVal);}
}

此类为动态数据源初始化类,以及获取数据源锁。

@Slf4j
@Component
@DependsOn({"dynamicDataSource"})
public class DataSourceChangeHandle {public static final String DATA_SOURCE = "DATA_SOURCE";private static final HashMap<String, String> dbDriverMap;private static final HashMap<String, String> dbTestQueryMap;private static final ReentrantLock dataSourceLock = new ReentrantLock(true);static {dbDriverMap = new HashMap<>(8);dbDriverMap.put("DB2", "com.ibm.db2.jcc.DB2Driver");dbDriverMap.put("MYSQL", "com.mysql.cj.jdbc.Driver");dbDriverMap.put("ORACLE", "oracle.jdbc.OracleDriver");dbTestQueryMap = new HashMap<>(8);dbTestQueryMap.put("DB2", "select 1 from sysibm.sysdummy1");dbTestQueryMap.put("MYSQL", "select 1");dbTestQueryMap.put("ORACLE", "select 1 from dual");}@Autowiredprivate DbParamConfMapper dbParamConfMapper;@Value("${minimumIdle:2}")private int minimumIdle;@Value("${maximumPoolSize:5}")private int maximumPoolSize;@Value("${idleTimeout:30000}")private int idleTimeout;@Value("${maxLifetime:60000}")private int maxLifetime;@Value("${connectionTimeout:3000}")private int connectionTimeout;public static Lock getDataSourceLock() {return dataSourceLock;}public static void lockDataSourceLock() {while (dataSourceLock.isLocked()) {//锁是可重入的,故如果已被锁则自旋等待Thread.yield();}dataSourceLock.lock();}@PostConstructpublic void initCustomDynamicDataSource() throws Exception {try {lockDataSourceLock();QueryWrapper<DbParamConf> queryWrapper = new QueryWrapper<>();queryWrapper.eq("EFF_FLG", "1");List<DbParamConf> list = dbParamConfMapper.selectList(queryWrapper);if (list == null || list.isEmpty()) {log.info("查询数据库配置信息为空,不初始化。");return;}DynamicDataSource.closeAllDataSource();for (DbParamConf dbInf : list) {if (null==dbInf||"MYSQL".equals(dbInf.getDbType())){continue;}HikariDataSource hikariDataSource = buildDataSource(dbInf);DynamicDataSource.initCustomDataSource(dbInf.getDbName() + "_" + dbInf.getDbType(), hikariDataSource);log.info("动态数据源初始化完成:" + dbInf.getDbName() + "," + dbInf.getDbType());}} catch (Exception e) {log.error("动态数据源初始化失败!", e);throw new Exception(e);} finally {dataSourceLock.unlock();}}public HikariDataSource buildDataSource(DbParamConf dbInf) {HikariDataSource hikariDataSource = new HikariDataSource();hikariDataSource.setJdbcUrl(dbInf.getJdbcUrl());hikariDataSource.setDriverClassName(dbDriverMap.get(dbInf.getDbType().trim().toUpperCase()));hikariDataSource.setUsername(dbInf.getDbUser());hikariDataSource.setPassword(dbInf.getDbPwd());hikariDataSource.setMinimumIdle(minimumIdle);hikariDataSource.setMaximumPoolSize(maximumPoolSize);hikariDataSource.setAutoCommit(false);hikariDataSource.setIdleTimeout(idleTimeout);hikariDataSource.setMaxLifetime(maxLifetime);hikariDataSource.setConnectionTimeout(connectionTimeout);hikariDataSource.setConnectionTestQuery(dbTestQueryMap.get(dbInf.getDbType().trim().toUpperCase()));return hikariDataSource;}
}
spring:datasource:druid:db01:url: jdbc:oracle:thin:@//username: password: driver-class-name: oracle.jdbc.OracleDriverinitial-size: 5max-active: 20min-idle: 5time-between-eviction-runs-millis: 2000min-evictable-idle-time-millis: 30000db02:url: jdbc:db2://username: password: driver-class-name: com.ibm.db2.jcc.DB2Driverinitial-size: 5max-active: 20min-idle: 5time-between-eviction-runs-millis: 2000min-evictable-idle-time-millis: 30000validation-query: SELECT 1 FROM sysibm.sysdummy1

此类为当db2的sql与oracle不兼容时,dao层bean需要重新注入

@Aspect
@Component
@Slf4j
public class ChangeMapperAspect extends ApplicationObjectSupport {@Pointcut("@annotation(com.annotation.RequiredChange)")public void changeAspect() {};@Around("changeAspect()")public Object change(ProceedingJoinPoint jp) throws Throwable {try {String dataSource = DynamicDataSource.getDataSource();//1.3.1获取目标对象类型Class<?> targetCls = jp.getTarget().getClass();//1.3.2获取目标方法MethodSignature ms = (MethodSignature) jp.getSignature();String methodName = ms.getName();Method targetMethod = targetCls.getMethod(methodName, ms.getParameterTypes());//1.3.3获取目标方法上的注解RequiredChange requiredChange = targetMethod.getAnnotation(RequiredChange.class);Class target = requiredChange.target();String init = requiredChange.init();DefaultListableBeanFactory autowireCapableBeanFactory = (DefaultListableBeanFactory) this.getApplicationContext().getAutowireCapableBeanFactory();Object bean = autowireCapableBeanFactory.getBean(target);Field field = targetCls.getDeclaredField(init);field.setAccessible(true);if (dataSource.equals("CCMDB_ORACLE")) {field.set(jp.getTarget(), bean);} else if (dataSource.equals("MCSDB_DB2")) {bean = autowireCapableBeanFactory.getBean(init);field.set(jp.getTarget(), bean);}return jp.proceed();} catch (Throwable e) {log.error("Mapper重新注入失败:cause {}", e.getMessage());throw e;}}}

注解类,TARGET为目标bean类型,init为dao层bean的name

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredChange {Class target() default Object.class;String init() default "";
}

通过切面动态切换数据源相关推荐

  1. mybatis手动切换数据库_在Spring项目中使用 Mybatis 如何实现动态切换数据源

    在Spring项目中使用 Mybatis 如何实现动态切换数据源 发布时间:2020-11-17 16:20:11 来源:亿速云 阅读:108 作者:Leah 这篇文章将为大家详细讲解有关在Sprin ...

  2. Springboot/MybatisPlus动态切换数据源

    1.1 简述 最近项目中有动态切换数据源需求,主要是要动态切换数据源进行数据的获取,现将项目中实现思路及代码提供出来,供大家参考.当然切换数据源还有其他的方式比如使用切面的方式,其实大体思路是一样的. ...

  3. SpringBoot+AOP实现动态切换数据源

    首先描述下笔者的基本步骤: 1.yml配置文件中定义多数据源: 2.自定义一个注解类(@DataSource 可标注在类或方法上),定义String类型的变量value,value中存储数据源名称: ...

  4. 动态切换数据源(spring+hibernate)

    起因:在当前我手上的一个项目中需要多个数据源,并且来自于不同类型的数据库... 因为很多历史原因.这个项目的住数据源是MySQL,整个系统的CURD都是操作的这个数据库. 但是还有另外两个用于数据采集 ...

  5. Spring学习总结(16)——Spring AOP实现执行数据库操作前根据业务来动态切换数据源

    深刻讨论为什么要读写分离? 为了服务器承载更多的用户?提升了网站的响应速度?分摊数据库服务器的压力?就是为了双机热备又不想浪费备份服务器?上面这些回答,我认为都不是错误的,但也都不是完全正确的.「读写 ...

  6. Spring Boot多数据源配置并通过注解实现动态切换数据源

    文章目录 1. AbstractRoutingDataSource类介绍 2. ThreadLocal类介绍 3. 环境准备 3.1 数据库准备 3.2 项目创建 4. 具体实现 4.1 定义数据源枚 ...

  7. Java实现动态切换数据源

    在一般的Java项目中,如果使用Spring去管理数据库连接信息,一般只能连接一个数据库,可是会有部分情况我们需要连接多个数据库,甚至还会存在不同的请求需要根据配置信息连接不同的数据库,比如: 在很多 ...

  8. springboot 中动态切换数据源(多数据源应用设计)

    目录 原理图 数据库 项目结构 启动类 entity controller service mapper 配置文件 线程上下文 (DataSourceHolder) 动态数据源 DynamicData ...

  9. spring boot 动态切换数据源实现多租户开发

    之前的文章有介绍过spring boot 动态切换数据源spring boot 动态切换数据源(数据源信息从数据库中读取)_lgq2016的博客-CSDN博客,今天简单介绍一下动态数据源切换实战,主要 ...

最新文章

  1. Redis 实现限流的三种方式
  2. zblog php版调用代码,zblogphp调用指定单篇文章代码升级版
  3. openwrt 19 overlay 空间不足_重视 | 山西一矿井瓦斯爆炸,有限空间作业切记注意安全...
  4. java对象占用内存大小?
  5. java类中获取全局变量_java 通过反射获取类的全局变量、方法、构造方法
  6. Spring Boot + Spring Cloud 构建微服务系统(三):服务消费和负载(Feign)
  7. android复位机器人图片_Universal-Image-Loader 图片异步加载类库还不熟?
  8. python之验证身份证号合法性的库:id_validator
  9. 我对未来技术趋势的一些看法
  10. 【目标定位】基于matlab UWB卡尔曼滤波追踪无线时钟同步误差【含Matlab源码 1626期】
  11. uva 11234 Expressions
  12. usaconbsp;chapternbsp;2.1nbsp;castle
  13. GMV远超预期背后,快手电商做对了什么?
  14. 接口:基于FPGA的HDMI接口设计
  15. 【速达软件】【速达3000】新账套导入旧账套资料SQL
  16. 单行/多行文本溢出的省略样式
  17. Android应用内跳转Scheme协议
  18. 教你如何查询车辆出险记录
  19. 同步AOKP源码的方法
  20. Android多点触控之——MotionEvent(触控事件)

热门文章

  1. 二次函数 用matlab,这个二次函数如何在MATLAB中拟合出来?
  2. 吃喝玩乐还能赚钱,这个行业高薪不看出身!
  3. 【HMS】地图服务我的位置定位问题
  4. 千投量化体验:平台介绍篇
  5. 数学笔记26——参数方程
  6. 生活中的嵌入式,你家的电视已经周围的LED屏幕
  7. [SHOI2003]吃豆豆(dp+拓扑排序)
  8. 复现《NC》图表(二):R语言一键画表达量箱线图并添加显著性
  9. 关注年底“高送转”概念
  10. 那些曾伴我走过编程之路的软件(转)