springboot使用APO动态切换整合多数据源(主从库切换或者mysql+Oracle整合开发)解决方案

完整整合多数据源切换demo实例,通过自定义注解实现

文章目录

  • 一、引入依赖
  • 二、编写yml文件
  • 三、编写枚举类列举数据库名
  • 四、编写配置类
    • 1.编写DataSourceConfig读取配置文件的数据库配置信息注入容器
    • 2.编写处理动态数据源
    • 3.实现动态切换
  • 四、编写注解
  • 、编写DataSourceAsepct切面类
  • 七、使用注解在serviceImpl上声明实现数据源的动态切换


一、引入依赖

引入springboot相关依赖这里选用的2.5.3版本:

    

        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            2.0.1
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            mysql
            mysql-connector-java
            5.1.34
            runtime
        
        
            com.baomidou
            mybatis-plus
            3.2.0
        
        
            org.springframework.boot
            spring-boot-starter-aop
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
        
            com.alibaba
            druid
            1.1.10
        
        
            org.projectlombok
            lombok
            true
        
        
            commons-lang
            commons-lang
            2.6
        
        
        
            com.alibaba
            fastjson
            1.2.72
        
        
            commons-codec
            commons-codec
            1.15
        
    

二、编写yml文件

本案例选用的是双mysql所以需要使用Oracle的需要在pom引入Oracle依赖然后更改驱动:

配置如下:

spring:
  datasource:
    appdb:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://192.168.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      username: root
      password: 123
      type: com.alibaba.druid.pool.DruidDataSource
      druid:
        max-active: 30 # 最大连接数
        min-idle: 5    # 最小连接量
        max-wait: 10000 # 最大等待时间 10s
        validation-query: SELECT 'WTUCLOUD' # 8小时空闲查询一次 WTUCloud 避免连接关闭
        time-between-eviction-runs-millis: 60000 # 空闲连接检查间隔
        min-evictable-idle-time-millis: 300000  # 空闲阈值
    sysdb:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://192.168.0.5:3306/mytest?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      username: root
      password: 123
      type: com.alibaba.druid.pool.DruidDataSource
      druid:
        max-active: 30 # 最大连接数
        min-idle: 5    # 最小连接量
        max-wait: 10000 # 最大等待时间 10s
        validation-query: SELECT 'WTUCLOUD' # 8小时空闲查询一次 WTUCloud 避免连接关闭
        time-between-eviction-runs-millis: 60000 # 空闲连接检查间隔
        min-evictable-idle-time-millis: 300000  # 空闲阈值

这里使用的配置一个名为appdb的数据库和一个sysdb的数据库,如果有更多可以依次类推在增加相关命名和配置即可


三、编写枚举类列举数据库名

这里使用了lombook插件生成构造方法;
实现代码如下:

package com.package.package.emenu;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 

Description: * 数据库枚举类 *

* * @author Editor MartinZac * @date 2021年07月27日 11:14 */ @AllArgsConstructor @Getter public enum DataSourceEnum { DEFAULT("sysdb"), APPDB("appdb"), SYSDB("sysdb"); private String name; }

四、编写配置类

编写将数据服务注入容器的配置实现代码如下:

1.编写DataSourceConfig读取配置文件的数据库配置信息注入容器

package com.package.package.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.sbm.productionmodelmap.emenu.DataSourceEnum;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

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

/**
 * 

Description:

* * @author Editor MartinZac * @date 2021年07月27日 11:14 */ @Configuration public class DataSourceConfig { /** * APP数据库配置 * @return */ @Bean("appDb") @ConfigurationProperties("spring.datasource.appdb") public DataSource appDb(){ return new DruidDataSource(); } /** * 后台系统数据库 * @return */ @Bean("sysDb") @ConfigurationProperties("spring.datasource.sysdb") public DataSource sysDb(){ return new DruidDataSource(); } @Bean("dynamicDataSource") @Primary public DataSource dynamicDataSource() { DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setDefaultTargetDataSource(appDb()); Map dataSourceMap = new HashMap<>(2); dataSourceMap.put(DataSourceEnum.APPDB.getName(), appDb()); dataSourceMap.put(DataSourceEnum.SYSDB.getName(), sysDb()); dataSource.setTargetDataSources(dataSourceMap); return dataSource ; } @Bean public PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dynamicDataSource()); } }

2.编写处理动态数据源

package com.package.package.config;

import com.sbm.productionmodelmap.emenu.DataSourceEnum;

/**
 * 

Description: * 动态数据源切换配置 *

* * @author Editor MaYongHui * @date 2021年07月27日 11:28 */ public class DataSourceHolder { private static final ThreadLocal DS_HOLDER = new ThreadLocal<>(); public static void setDataSource(DataSourceEnum dataSource) { DS_HOLDER.set(dataSource.getName()); } public static String getDataSource() { return DS_HOLDER.get(); } public static void clearDataSource() { DS_HOLDER.remove(); } }

3.实现动态切换

package com.package .package .config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 

Description:

* * @author Editor MarticZac * @date 2021年07月27日 11:26 */ @Slf4j public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { //实现数据源切换 log.info("dynamic datasource :" + DataSourceHolder.getDataSource()); return DataSourceHolder.getDataSource(); } }

四、编写注解

自定义注解实现代码如下:


package com.package .package .target;


import com.package .package .emenu.DataSourceEnum;

import java.lang.annotation.*;

/**
 * 

Description:

* * @author Editor MartinZac * @date 2021年07月27日 11:14 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface DataSource { //获取枚举类的值作为value DataSourceEnum value(); }

、编写DataSourceAsepct切面类

切面类通过注解作为切点获取注解参数调用handler实现数据源切换:

package com.package .package .asepct;

import com.package .package .target.DataSource;
import com.package .package .emenu.DataSourceEnum;
import com.package .package .config.DataSourceHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 

Description: 动态切换数据源切面类 @Order(1) 注解为开启切点顺序,由于还有其他切点处理日志需要开启顺序

* * @author Editor MartinZac * @date 2021年07月27日 11:34 */ @Aspect @Component @Order(1) @Slf4j public class DataSourceAsepct{ @Pointcut("@annotation(com.sbm.productionmodelmap.target.DataSource)") public void pointCut() { } @Before("pointCut()") public void before(JoinPoint joinPoint) { Object target = joinPoint.getTarget(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); DataSourceEnum dataSourceEnum = DataSourceEnum.DEFAULT; try { Method method = target.getClass().getMethod(signature.getName(), signature.getParameterTypes()); if (method.isAnnotationPresent(DataSource.class)) { DataSource annotation = method.getAnnotation(DataSource.class); dataSourceEnum = annotation.value(); } } catch (NoSuchMethodException e) { e.printStackTrace(); } log.info("切换数据源为: " + dataSourceEnum.getName()); //切换数据源 DataSourceHolder.setDataSource(dataSourceEnum); } @After("pointCut()") public void after() { DataSourceHolder.clearDataSource(); } }

七、使用注解在serviceImpl上声明实现数据源的动态切换

以上6个步骤已经完全实现了数据源切换的所有逻辑编写,接下来的实现只是使用注解在业务方法上实现数据源切换处理不通的业务逻辑:

package com.sbm.productionmodelmap.service.impl;

import com.sbm.productionmodelmap.emenu.DataSourceEnum;
import com.sbm.productionmodelmap.entity.OperLog;
import com.sbm.productionmodelmap.mapper.OperLogMapper;
import com.sbm.productionmodelmap.service.OperLogService;
import com.sbm.productionmodelmap.target.DataSource;
import com.sbm.productionmodelmap.vo.OperLogVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 

Description: 通过 @DataSource(DataSourceEnum.APPDB) 注解声明哪个方法应该使用哪个数据库来实现具体的调用,切面类通过切点获取注解进行自动切换

* * @author Editor MaYongHui * @date 2021年08月23日 15:31 */ @Service public class TestServiceImpl implements TestService { @Autowired privateTestAppMapper testAppMapper; @Autowired privateTestSysMapper testSysMapper; @DataSource(DataSourceEnum.APPDB) @Override public int deleteByPrimaryKey(Long operId) { return testAppMapper.deleteByPrimaryKey(operId); } @DataSource(DataSourceEnum.SYSDB) @Override public int insert(OperLog record) { return testSysMapper.insert(record); } }

以上所有步骤为实现多数据源动态切换的方式,可以根据自己业务选择数据源的切换实现主从读写业务或其他复杂业务,但是目前存在一个使用环境暂时没有更好的解决方案,当你的项目中还有其他切面类处理日志等其他业务需要切换数据源时会出现动态切换失败一直为枚举类中的默认数据源,我查看过一些失效的解决办法网上大部分都为通过使用@Order注解来定义切点的执行顺序将数据源的执行顺序放到最高级(@Order(1)这个数据越大执行顺序越低。数值越小执行顺序越高),这样的方式从AOP思想上来看是并没有问题的,但是使用后还是不能解决切换失败的问题,所以我自己是将需要处理日志的数据源设置为默认,但并没有解决根本问题。所以欢迎大佬遇到的时候能评论区给个指点!

你可能感兴趣的:(java,服务器,oracle,mysql,spring,boot)