基于spring-boot2.0.0版本实现多数据源注册
功能点:一,可以通过配置控制数据源注册个数,实现事先不知道数据源个数和别名,在不修改任何有关数据库相关代码条件下,仅仅在使用时按照规则添加配置来注册多数据源.可以将其作为基础jar提供公共服务.
二,实现了多数据源中,调用单个数据源的事务控制(同一个方法中只调用一个数据库的事务),同一个方法中调用不同数据源就是分布式事务,以下代码没有实现.
实现代码:
1,继承AbstractRoutingDataSource重写determineCurrentLookupKey方法,得到通过AOP切换之后的数据源名称
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 功能说明: 通过重写determineCurrentLookupKey得到切换之后的数据源名称
* 系统版本: v1.0
* 开发人员: @author liansh
* 开发时间: 2019年6月25日
*/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal contextHolder = new ThreadLocal();
@Override
protected Object determineCurrentLookupKey() {
log.debug("数据源为:{}", DynamicDataSource.getDataSource());
return DynamicDataSource.getDataSource();
}
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
2,添加注解,在方法上声明该注解,指定数据源,不添加注解就会使用默认数据源
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 功能说明: 数据源
* 系统版本: v1.0
* 开发人员: @author liansh
* 开发时间: 2019年6月25日
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
String defaultStr = "default";
String value() default defaultStr;
}
3,读取数据源相关配置,通过数据库别名,动态读取具有相同特征(spring.datasource.xxx.username,xxx作为别名)的数据库相关配置并注册进spring容器,旨在将此类作为jar,不管扩展多少个未知数据源,都可以不用修改数据源相关代码
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import com.alibaba.druid.pool.DruidDataSource;
/**
* 功能说明: 动态数据源配置,配合注解可完成数据库自动切换,但切换必须在service以上,不能在mapper层
* 系统版本: v1.0
* 开发人员: @author liansh
* 开发时间: 2019年6月25日
*/
@Slf4j
@Configuration
public class DataSourceConfig {
@Value("${jdbc.mapper-locations:classpath*:**/*Mapper.xml}")
private String mappingPath;
/** 需要注册的数据库别名列表 */
@Value("${jdbc.target-sources:default,order}")
private String targetSources = "";
@Autowired
private Environment env;
private Map
4,注册AOP拦截,前置切面完成切换数据源,后置切面清空数据源,必须添加@Order注解,使该切面先于事务切面执行,否则会造成开启事务情况下切换数据源失败
package com.lsh.jdbc.dataSource;
import java.lang.reflect.Method;
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.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 功能说明: 数据源切换Aop切面,作用:在方法之前完成数据源的切换
* 如果该切面不添加@Order注解,那么事务切面将在切换数据源之前执行,导致切换数据源失败
* 系统版本: v1.0
* 开发人员: @author liansh
* 开发时间: 2019年6月25日
*/
@Order(1)
@Aspect
@Component
public class DynamicDataSourceAspect {
@Before("@annotation(DS)")
public void beforeSwitchDS(JoinPoint point) {
// 获得当前访问的class
Class> className = point.getTarget().getClass();
// 获得访问的方法名
String methodName = point.getSignature().getName();
// 得到方法的参数的类型
Class>[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();
String dataSource = DS.defaultStr;
try {
// 得到访问的方法对
Method method = className.getMethod(methodName, argClass);
DS interceptor = className.getAnnotation(DS.class);
if (interceptor != null) {// 判断类上是否存在@DS注解
// 取出注解中的数据源名
dataSource = interceptor.value();
} else if (method.isAnnotationPresent(DS.class)) {// 判断方法上是否存在@DS注解
DS annotation = method.getAnnotation(DS.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
DynamicDataSource.setDataSource(dataSource);
}
@After("@annotation(DS)")
public void afterSwitchDS(JoinPoint point) {
DynamicDataSource.clearDataSource();
}
}
完整代码示例