最近接到个任务,需要把原来的单数据源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>
然后就可以修改配置文件里面的数据源信息了
第二步:创建数据源配置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。。。