简介
动态数据源,支持多企业多数据源,支持读写分离设计,服务运行过程中,支持动态加入新数据源
实现步骤
动态数据源上下文
import com.alibaba.ttl.TransmittableThreadLocal;
/**
* kafka通讯协议上下文 需配合TtlExecutors 修饰线程池使用
*/
public class DatabaseContextHolder {
//TransmittableThreadLocal可实现多线程父子线程变量传递,也可以实现线程池内变量传递,线程池需要TtlExecutors修饰
private final static TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
public static String getDatabae(){
return threadLocal.get();
}
public static void setDatabase(String database){
threadLocal.set(database);
}
public static void remove(){
mvc拦截器,通过token获取用户企业信息,然后得到企业数据库,设置本次请求动态数据源上下文
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//自定义Mvc请求拦截器
@Component
@Slf4j
public class DabaseInterceptor extends HandlerInterceptorAdapter {
private static final String TRACE_ID = "TRACE_ID";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//设置动态数据源缓存
try{
//根据请求用户信息获取用户对应的数据 根据自身业务逻辑补充
String database = "";
DatabaseContextHolder.setDatabase(dabase);
}catch (Exception e){
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
DatabaseContextHolder.remove();
}
}
@Configuration
class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DabaseInterceptor());
}
}
动态数据源切面, 切service方法,将数据源上下文数据源设置到mybaits-plus动态数据源中
import cn.hutool.core.annotation.AnnotationUtil;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
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.stereotype.Component;
import java.lang.reflect.Method;
/**
* 动态数据源切面
*/
@Component
@Aspect
@Slf4j
public class DynamicDataSourceAspect {
//切面service包下所有类所有方法 不包括子包
//@Pointcut("execution(* com.service.*.*(..))")
//public void point(){};
//切面需要放在这里,解决mybaits-plus saveBatch无法设置动态数据源问题
@Pointcut("execution(* com.baomidou.dynamic.datasource.ds.AbstractRoutingDataSource.getConnection(..))")
public void point(){};
@Before(value = "point()")
public void before(JoinPoint joinPoint){
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
Method method = methodSignature.getMethod();
String ds;
if(AnnotationUtil.hasAnnotation(method, DynamicDs.class)){
DynamicDs dynamicDs = method.getAnnotation(DynamicDs.class);
DynamicDsEnum dynamicDsEnum = dynamicDs.value();
ds = DatabaseContextHolder.getDatabase + "_" + dynamicDsEnum.getCode();
}else {
//未加注解 默认使用写库
ds = DatabaseContextHolder.getDatabase + "_" + DynamicDsEnum.WRITE.getCode();
}
DynamicDataSourceContextHolder.push(ds);
}
}
动态数据源自定义注解,在方法上加上注解,可以实现读写分离
/**
* 动态数据源注解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicDs {
//默认写库
DynamicDsEnum value() default DynamicDsEnum.WRITE;
}
数据源类型枚举类
/**
* 数据源类型
*/
@Getter
public enum DynamicDsEnum {
WRITE("1", "写"),
READ("2", "读");
private String code;
private String desc;
DynamicDsEnum(String code, String desc){
this.code = code;
this.desc = desc;
}
}
服务启动从数据源配置表加载动态数据源
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.List;
@Component
@Slf4j
public class DataSourceLoadHandler {
@Value("${gocom.initDataSource:true}")
private boolean initDataSource;
private ISysDatabaseClient sysDatabaseClient;
private DefaultDataSourceCreator dataSourceCreator;
private DynamicRoutingDataSource routingDataSource;
@Resource
public void setRoutingDataSource(DynamicRoutingDataSource dataSource){
this.routingDataSource = dataSource;
}
@Resource
public void setDefaultDataSourceCreator(DefaultDataSourceCreator sourceCreator){
this.dataSourceCreator = sourceCreator;
}
@Resource
public void setSysDatabaseClient(ISysDatabaseClient sysDatabaseClient) {
this.sysDatabaseClient = sysDatabaseClient;
}
@PostConstruct
public void initDataSources(){
if(!initDataSource){
log.info("不加载企业动态数据源");
return;
}
log.info("############开始加载企业数据源#################");
List<String> list = null;//查询所有数据源配置库
if(CollUtil.isEmpty(list)){
return;
}
list.forEach(sysCompanyDatabases -> {
try{
DataSourceProperty dataSourceProperty = JSON.parseObject(sysCompanyDatabases.getConfigs(), DataSourceProperty.class);
dataSourceProperty.setPoolName("client-ds-" + sysCompanyDatabases ;
DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty);
routingDataSource.addDataSource(sysCompanyDatabases , dataSource);
log.info("企业数据源加载成功:{}", sysCompanyDatabases.getId());
}catch (Exception e){
log.error("企业数据源加载异常:{}", sysCompanyDatabases.getId());
}
});
log.info("############加载企业数据源完成#################");
}
}