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

本文档详细介绍了如何在Spring Boot项目中实现多数据源动态切换,包括引入依赖、配置YML文件、定义枚举类、配置数据源、编写注解和切面类,以及在Service层使用注解进行数据源切换。通过这种方式,可以灵活地根据业务需求选择不同的数据库进行操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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


一、引入依赖

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

    <dependencies>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.34</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- alibaba durid 数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.15</version>
        </dependency>
    </dependencies>

二、编写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;

/**
 * <p>Description:
 * 数据库枚举类
 * </p>
 *
 * @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;

/**
 * <p>Description: </p>
 *
 * @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<Object, Object> 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;

/**
 * <p>Description:
 * 动态数据源切换配置
 * </p>
 *
 * @author Editor MaYongHui
 * @date 2021年07月27日 11:28
 */
public class DataSourceHolder {

    private static final ThreadLocal<String> 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;

/**
 * <p>Description: </p>
 *
 * @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.*;

/**
 * <p>Description: </p>
 *
 * @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;

/**
 * <p>Description: 
动态切换数据源切面类
@Order(1) 注解为开启切点顺序,由于还有其他切点处理日志需要开启顺序
</p>
 *
 * @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;

/**
 * <p>Description: 通过 @DataSource(DataSourceEnum.APPDB) 注解声明哪个方法应该使用哪个数据库来实现具体的调用,切面类通过切点获取注解进行自动切换</p>
 *
 * @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思想上来看是并没有问题的,但是使用后还是不能解决切换失败的问题,所以我自己是将需要处理日志的数据源设置为默认,但并没有解决根本问题。所以欢迎大佬遇到的时候能评论区给个指点!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值