最近接到个任务,需要把原来的单数据源SpringBoot项目,调整为多数据源,且改动要尽量小,能不改以前的代码是最好。

最终实现参考了多个资料,在最后切面处理的时候给了很大帮助的是这篇博客,很详细,

OK,接下来进入正题,由于目前的SpingBoot项目是2.0以上的,所以连接池默认是Hikari,而多数据源很多都推荐用Druid,所以,第一步,就是把原来的数据库配置连接池改成Druid。

操作:引入依赖,推荐用这个,只能说基本引进来,数据源的配置就不会报错了。

<!--  druid相关 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.22</version>
</dependency>

然后就可以修改配置文件里面的数据源信息了

springboot 属性绑定自定义类型转换_后端

 第二步:创建数据源配置DataSourceConfig、动态数据源路由配置类DynamicDataSource,代码分别如下:

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.default")
    public DataSource dataSourceDefault() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.other")
    public DataSource dataSourceOther() {
        return DruidDataSourceBuilder.create().build();
    }

}
import com.hero.enmu.dataSource.DataSourceEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;

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

@Component
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {

    // 用于记录当前数据源的key
    public static ThreadLocal<String> dataSourceName = new ThreadLocal<>();

    @Autowired
    DataSource dataSourceDefault;

    @Autowired
    DataSource dataSourceOther;

    /**
     * 必须重写父类方法,返回数据源配置.
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return dataSourceName.get();
    }

    /**
     * 重写父类方法,对数据源配置进行初始化.
     */
    @Override
    public void afterPropertiesSet(){
        // 初始化targetDataSources-把所有的数据源放进去,key值,在后面注解中会用到,记得用枚举
        Map<Object, Object> targetDataSources = new HashMap<>(2);
        targetDataSources.put("default", dataSourceDefault);
        targetDataSources.put("other", dataSourceOther);
        super.setTargetDataSources(targetDataSources);
        // 设置默认数据库
        super.setDefaultTargetDataSource(dataSourceDefault);
        super.afterPropertiesSet();
    }

    /**
     * 设置数据源.
     * @param dataSource 数据源名称
     */
    public static void setDataSource(String dataSource) {
        dataSourceName.set(dataSource);
    }

    /**
     * 移除数据源配置.
     */
    public static void clearDataSource() {
        dataSourceName.remove();
    }
}

第三步:创建一个自定义注解DynamicDataSourceAnnotation(后面想要切换数据源的时候,在对应方法上面加上注解即可)、一个切面处理类DynamicDataSourceAspect(负责对标明注解的方法进行增强,切换数据源之类的操作)、名字看个人习惯创建即可。

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicDataSourceAnnotation {
    String name() default "";
}
import com.annotation.DynamicDataSourceAnnotation;
import com.start.DynamicDataSource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @Desc: 动态数据源切换切面处理.
 */
@Component
@Aspect
@Slf4j
public class DynamicDataSourceAspect {

    // 上面创建注解的路径 
    @Pointcut("@annotation(com.annotation.DynamicDataSourceAnnotation)")
    public void dataSourcePointCut() {}

    /**
     * 环绕触发(包含前置Before)
     * @param point 切入点
     * @return Object
     * @throws Throwable 异常
     */
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DynamicDataSourceAnnotation ds = method.getAnnotation(DynamicDataSourceAnnotation.class);
        if(ds == null){
            // 如果没有注解,使用默认数据源
            DynamicDataSource.setDataSource("default");
        }else {
            // 根据注解中设置的数据源名称,选择对应的数据源
            DynamicDataSource.setDataSource(ds.name());
            log.info("set datasource is " + ds.name());
        }

        try {
            return point.proceed();
        } finally {
            // 清除数据源配置
            DynamicDataSource.clearDataSource();
        }
    }
}

在这里,如果@Aspect注解报错了,那么就需要添加AOP依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

这样就搞定了,没有其他配置,在对应要切换数据源的serviceImpl类的方法上加上刚才新建的自定义注解,name值对应要切换的数据源即可,如果没有加注解的,就都是用默认的数据源。

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private TestMapper testMapper;

    @Override
    @DynamicDataSourceAnnotation(name = "other")
    public List<TestData> select(Long limit, Long offset) {
        WebUtil.setPageParams(offset.intValue(), limit.intValue());
        return testMapper.select(limit, offset);
    }

}

最后,重启服务,完美解决,这个不只是用于两个相同的数据库,其他数据库加起来,基本也是一样的操作,唯一有不同的可能是引入的驱动,依赖,还有就是Mapper层的处理,下一篇再更新其他数据库的多数据源。

踩的坑:Druid的依赖,用原生的有很多报错,后面改成这个兼容springboot版本的,才搞定。还有就是spring.datasource.default.maxActive要按需调整,如果业务量大,记得调整大一些,不然会druid报错The error occurred while executing a query。。。