注:数据源注入方式以AOP方式写入
一、首先获取当前配置文件默认数据源及数据库中配置的所有数据源
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.sql.DataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
/**
* 数据源配置类
*
* @author Lynch
*/
@Slf4j
@Configuration
public class DataSourceComponent implements EnvironmentAware {
private Environment evn;
/**
* 别名
*/
private final static ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
/**
* 由于部分数据源配置不同,所以在此处添加别名,避免切换数据源出现某些参数无法注入的情况
*/
static {
aliases.addAliases("url", new String[]{"jdbc-url"});
aliases.addAliases("username", new String[]{"user"});
}
@Primary//不加这个会报错。
@Bean(name = "multiDataSource")
public MultiRouteDataSource exampleRouteDataSource() {
MultiRouteDataSource multiDataSource = new MultiRouteDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
// 获取所有数据源配置
String typeStr = evn.getProperty("spring.datasource.dynamic.datasource.master.type");
String url = evn.getProperty("spring.datasource.dynamic.datasource.master.url");
String name = evn.getProperty("spring.datasource.dynamic.datasource.master.name");
String username = evn.getProperty("spring.datasource.dynamic.datasource.master.username");
String password = evn.getProperty("spring.datasource.dynamic.datasource.master.password");
Map defauleDataSourceProperties = new HashMap();
defauleDataSourceProperties.put("url",url);
defauleDataSourceProperties.put("name",name);
defauleDataSourceProperties.put("username",username);
defauleDataSourceProperties.put("password",password);
evn.getProperty("spring.datasource.dynamic.datasource.master");
Class<? extends DataSource> clazz = getDataSourceType(typeStr);
// 绑定默认数据源参数 也就是主数据源
System.out.println("当前数据源"+defauleDataSourceProperties);
DataSource consumerDatasource, defaultDatasource = bind(clazz, defauleDataSourceProperties);
targetDataSources.put("master", defaultDatasource);
// 获取其他数据源配置
Connection conn = null;
try {
conn = defaultDatasource.getConnection();
String sql = "select * from sys_tenant ";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
ResultSet rs = preparedStatement.executeQuery();
while (rs.next()) {
String key = rs.getString("id");
defauleDataSourceProperties.put("name", key);
defauleDataSourceProperties.put("url", rs.getString("url"));
defauleDataSourceProperties.put("username", rs.getString("username"));
defauleDataSourceProperties.put("password", rs.getString("password"));
// 绑定参数
consumerDatasource = bind(clazz, defauleDataSourceProperties);
targetDataSources.put(key, consumerDatasource);
}
rs.close();
preparedStatement.close();
conn.close();
} catch (SQLException e) {
log.error(e.getMessage());
}
// 将该bean注册为datasource,不使用springboot自动生成的datasource
log.info("注册数据源成功,一共注册{}个数据源", targetDataSources.keySet().size());
multiDataSource.setTargetDataSources(targetDataSources);
multiDataSource.setDefaultTargetDataSource(defaultDatasource);
return multiDataSource;
}
/**
* 注册ServletRegistrationBean
*
* @return
*/
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*");
reg.addInitParameter("allow", ""); // 白名单 return reg;
reg.addInitParameter("loginUsername", "duan");
reg.addInitParameter("loginPassword", "123456");
reg.addInitParameter("resetEnable", "false");
return reg;
}
/**
* 注册FilterRegistrationBean
*
* @return
*/
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
filterRegistrationBean.addInitParameter("profileEnable", "true");
filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
filterRegistrationBean.addInitParameter("DruidWebStatFilter", "/*");
return filterRegistrationBean;
}
/**
* 通过字符串获取数据源class对象
*
* @param typeStr
* @return
*/
private Class<? extends DataSource> getDataSourceType(String typeStr) {
Class<? extends DataSource> type;
try {
if (StringUtils.hasLength(typeStr)) {
// 字符串不为空则通过反射获取class对象
type = (Class<? extends DataSource>) Class.forName(typeStr);
} else {
// 默认为hikariCP数据源,与springboot默认数据源保持一致
type = HikariDataSource.class;
}
return type;
} catch (Exception e) {
throw new IllegalArgumentException("can not resolve class with type: " + typeStr); //无法通过反射获取class对象的情况则抛出异常,该情况一般是写错了,所以此次抛出一个runtimeexception
}
}
private <T extends DataSource> T bind(Class<T> clazz, Map properties) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
// 通过类型绑定参数并获得实例对象
return binder.bind(ConfigurationPropertyName.EMPTY, Bindable.of(clazz)).get();
}
@Override
public void setEnvironment(Environment environment) {
this.evn = environment;
}
}
二、未注入数据源所需要的工具类及配置文件
/**
* 数据源上下文
*
* @author Lynch
*/
public class DataSourceContext {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String value) {
contextHolder.set(value);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* DataSource路由类
*
* 重写的函数决定了最后选择的DataSource
*
* @author Lynch
*/
public class MultiRouteDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 通过绑定线程的数据源上下文实现多数据源的动态切换,有兴趣的可以去查阅资料或源码
return DataSourceContext.getDataSource();
}
}
三、添加APO拦截所有controller请求
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
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.jeecg.common.system.vo.LoginUser;
import org.jeecg.config.mybatis.config.DataSourceContext;
import org.jeecg.config.mybatis.util.IgnoreDynamicData;
import org.jeecg.modules.system.service.ISysTenantService;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.annotation.Annotation;
@Component
@Aspect
@Order(-1)
@Slf4j
public class BaseDynamicDataSourceAspect {
@Resource
private ISysTenantService sysTenantService;
/**
* 普通Controller切面(interfaces)
*/
@Pointcut("execution(public * org.jeecg.modules.interfaces.*.controller.*.*(..))")
public void addInterfaces(){}
/**
* 普通Controller切面(cas、message、moitor、ngalain、oss、quartz、system)
*/
@Pointcut("execution(public * org.jeecg.modules.*.controller.*.*(..))")
public void addCas(){}
@Around("addInterfaces() || addCas()")
public Object Interceptor(ProceedingJoinPoint pjp){
Object result = null;
MethodSignature signature = (MethodSignature)pjp.getSignature();
Annotation anoIgnore=signature.getMethod().getAnnotation(IgnoreDynamicData.class);
try {
//如果是bpm操作业务数据 获取方法中的数据源参数作为拦截目标
if(anoIgnore!=null){
result=pjp.proceed();
}else{
//获取当前用户
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
log.info("当前用户:"+sysUser);
//获取用户所对应的数据源
if(sysUser == null ){
log.error("获取动态数据源为空,使用默认数据源:master");
}else{
if(sysUser.getTenantId() == null || sysUser.getTenantId().equals("")){
log.error("获取动态数据源为空,使用默认数据源:master");
}else{
DataSourceContext.setDataSource(sysUser.getTenantId());
log.info("当前数据源ID:"+sysUser.getTenantId());
}
}
result=pjp.proceed();
}
} catch (Throwable throwable) {
log.error("获取动态数据源错误,还是原来的数据源");
return result;
}
return result;
}
}
说明:此处的tenantId与DataSourceComponent类中遍历绑定数据源中的key必须对应
四、AOP所需要的配置文件
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 动态数据源注解出现这个注解的方法。
* john
*/
@Target({ElementType.METHOD})
@Retention(RUNTIME)
@Documented
public @interface DynamicData {
String value() default "";
String dbname() default "";
String schema() default "";
}
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 需要被忽略动态数据源的方法
* john
*/
@Target({ElementType.METHOD})
@Retention(RUNTIME)
@Documented
public @interface IgnoreDynamicData {
String value() default "";
}
流程说明:
1.项目初始化加载默认数据源。
2.根据默认数据元查询数据库中配置的所有数据源进行初始化操作
3.使用AOP与Around将所有Controller中的请求进行拦截
4.LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
根据当前的登录用户(项目中设置的时全局的,用户登陆后回将登录用户设置未全局用户),查询用 户所配置的tenantId。并进行切换数据源