1、创建文件 ApplicationContextHandler.java
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextHandler implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHandler.context = applicationContext;
}
public static Object getBean(String name) {
return ApplicationContextHandler.context.getBean(name);
}
public static Object getContext() {
return ApplicationContextHandler.context;
}
}
2、创建 DynamicDataSource.java
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
* 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
*/
@Override
protected DataSource determineTargetDataSource() {
return super.determineTargetDataSource();
}
/**
* 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
/**
* 设置默认数据源
*
* @param defaultDataSource
*/
public void setDefaultDataSource(Object defaultDataSource) {
super.setDefaultTargetDataSource(defaultDataSource);
}
/**
* 设置数据源
*
* @param dataSources
*/
public void setDataSources(Map<Object, Object> dataSources) {
super.setTargetDataSources(dataSources);
// 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
}
}
3、创建DynamicDataSourceContextHolder.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
/**
* 将 master 数据源的 key作为默认数据源的 key
*/
@Override
protected String initialValue() {
return "dataSource";
}
};
/**
* 数据源的 key集合,用于切换时判断数据源是否存在
*/
public static List<Object> dataSourceKeys = new ArrayList<>();
/**
* 切换数据源
* @param key
*/
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
/**
* 获取数据源
* @return
*/
public static String getDataSourceKey() {
return contextHolder.get();
}
/**
* 重置数据源
*/
public static void clearDataSourceKey() {
contextHolder.remove();
}
/**
* 判断是否包含数据源
* @param key 数据源key
* @return
*/
public static boolean containDataSourceKey(String key) {
return dataSourceKeys.contains(key);
}
/**
* 添加数据源keys
* @param keys
* @return
*/
public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
return dataSourceKeys.addAll(keys);
}
}
4、创建 DynamicDataSourceInit.java
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.pool.DruidDataSource;
import com.lease.api.common.dao.CompanyDBDao;
import com.lease.api.common.model.CompanyDB;
import com.lease.api.common.util.JasyptUtil;
@Configuration
public class DynamicDataSourceInit {
/**
* LOG输出
*/
private static final Logger log = Logger.getLogger(DynamicDataSourceInit.class);
@Value("${jasypt.encryptor.password}")
private String password;
@PostConstruct
public void InitDataSource() {
log.info("=====初始化动态数据源=====");
DynamicDataSource dynamicDataSource =
(DynamicDataSource) ApplicationContextHandler.getBean("dynamicDataSource");
DruidDataSource dataSource =
(DruidDataSource) ApplicationContextHandler.getBean("dataSource");
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("dataSource", dataSource);
CompanyDBDao companyDBDao =
(CompanyDBDao) ApplicationContextHandler.getBean("companyDBDao");
List<CompanyDB> CompanyDBs = companyDBDao.findByDelete();
if (CompanyDBs != null && CompanyDBs.size() > 0) {
for (CompanyDB companyDB : CompanyDBs) {
log.info(companyDB.toString());
DruidDataSource ds = new DruidDataSource();
ds.setUrl(companyDB.getDatasourceUrl());
ds.setUsername(companyDB.getDatasourceUsername());// 用户名
ds.setPassword(JasyptUtil.decyptPwd(password, companyDB.getDatasourcePassword()));// 密码
ds.setInitialSize(1);
ds.setMaxActive(20);
ds.setMinIdle(0);
ds.setMaxWait(30000);
ds.setValidationQuery("select user()");
ds.setTestOnBorrow(false);
ds.setTestWhileIdle(true);
ds.setPoolPreparedStatements(false);
dataSourceMap.put(companyDB.getId(), ds);
}
}
// 设置数据源
dynamicDataSource.setDataSources(dataSourceMap);
/**
* 必须执行此操作,才会重新初始化AbstractRoutingDataSource 中的
* resolvedDataSources,也只有这样,动态切换才会起效
*/
dynamicDataSource.afterPropertiesSet();
}
}
5、创建切面文件DynamicDataSourceAspect.java
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.lease.api.common.bean.LoginInfo;
import com.lease.api.common.util.JSONUtil;
@Aspect
@Component
@Order(1) // 请注意:这里order一定要小于tx:annotation-driven的order,即先执行DynamicDataSourceAspectAdvice切面,再执行事务切面,才能获取到最终的数据源
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DynamicDataSourceAspect {
private static final Logger logger = Logger.getLogger(DynamicDataSourceAspect.class);
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Around("execution(* com.lease.api..*.*(..)) ")
public Object doAround(ProceedingJoinPoint jp) throws Throwable {
String token = getToken();
if (StringUtils.isBlank(token)) {
logger.info("未登录");
} else {
LoginInfo loginInfo = getLoginInfo(token);
if (loginInfo == null) {
logger.info("未登录");
} else {
String companyId = loginInfo.getStaff().getCompanyId();
logger.info("当前租户Id:" + companyId);
if (StringUtils.isNotBlank(companyId)) {
DynamicDataSourceContextHolder.setDataSourceKey(companyId);
}
}
}
Object result = jp.proceed();
DynamicDataSourceContextHolder.clearDataSourceKey();
return result;
}
/**
* 获取token
*
* @return
*/
public String getToken() {
ServletRequestAttributes servletRequestAttributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (servletRequestAttributes == null) return null;
HttpServletRequest request = servletRequestAttributes.getRequest();
if (request == null) return null;
String token = request.getHeader("Auth-Token");
logger.info("getLoginInfo() start. Auth-Token=" + token);
if (StringUtils.isBlank(token)) {
token = request.getParameter("token");
}
return token;
}
/**
* 根据token获取登录人
*
* @param token
* @return
*/
public LoginInfo getLoginInfo(String token) {
logger.info("getLoginInfo(token) start. token=" + token);
String json = redisTemplate.opsForValue().get(token);
if (StringUtils.isBlank(json)) {
return null;
} else {
return JSONUtil.toBean(json, LoginInfo.class);
}
}
}
6、配置动态数据源Bean
@Bean
@Primary
@Qualifier("dataSource")
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));// 用户名
dataSource.setPassword(env.getProperty("spring.datasource.password"));// 密码
dataSource.setInitialSize(1);
dataSource.setMaxActive(20);
dataSource.setMinIdle(0);
dataSource.setMaxWait(30000);
dataSource.setValidationQuery("select user()");
dataSource.setTestOnBorrow(false);
dataSource.setTestWhileIdle(true);
dataSource.setPoolPreparedStatements(false);
return dataSource;
// return DataSourceBuilder.create().build();
}
@Bean
@Qualifier("dynamicDataSource")
//@ConfigurationProperties(prefix = "spring.datasource.activiti")//项目中含有activity流程图需要添加此注解
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("dataSource", dataSource());
// 将 master 数据源作为默认指定的数据源
dynamicDataSource.setDefaultDataSource(dataSource());
// 将 master 和 slave 数据源作为指定的数据源
dynamicDataSource.setDataSources(dataSourceMap);
return dynamicDataSource;
}
7、创建文件配置Bean(hibernate)
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "entityManageFactoryPrimary",
transactionManagerRef = "transactionManagerPrimary",
basePackages = {"com.lease.api.common.dao"})
public class EntityManageConfig {
@Autowired
@Qualifier("dynamicDataSource")
private DataSource dynamicDataSource;
@Primary
@Bean(name = "entityManagerPrimary")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return entityManageFactory(builder).getObject().createEntityManager();
}
@Primary
@Bean(name = "entityManageFactoryPrimary")
public LocalContainerEntityManagerFactoryBean
entityManageFactory(EntityManagerFactoryBuilder builder) {
LocalContainerEntityManagerFactoryBean entityManagerFactory =
builder.dataSource(dynamicDataSource).packages("com.lease.api.common.model")// PathEnum自定义的路径枚举类
.persistenceUnit("primaryPersistenceUnit") // 使用持久化单元配置
.build();
return entityManagerFactory;
}
@Primary
@Bean(name = "transactionManagerPrimary")
public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManageFactory(builder).getObject());
}
}
8、创建文件配置Bean(mybatis)
import javax.sql.DataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement
@Configuration
@MapperScan({"com.lease.api.process.dao"}) // "org.activiti.engine.impl.persistence.entity"
public class MybatisPlusConfig {
@Autowired
@Qualifier("dynamicDataSource")
private DataSource dynamicDataSource;
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 配置数据源,此处配置为关键配置,如果没有将 dynamicDataSource作为数据源则不能实现切换
sessionFactory.setDataSource(dynamicDataSource);
// // 扫描Model
sessionFactory.setTypeAliasesPackage(// ,org.activiti.engine.impl.persistence.entity
"com.lease.api.process.entity");
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 扫描映射文件 ,classpath*:org/activiti/db/mapping/entity/*.xml
sessionFactory.setMapperLocations(resolver.getResources("classpath*:mybatis/*Mapper.xml"));
return sessionFactory;
}
@Bean
public PlatformTransactionManager transactionManager() {
// 配置事务管理, 使用事务时在方法头部添加@Transactional注解即可
return new DataSourceTransactionManager(dynamicDataSource);
}
}
总结:
数据库动态查询所有数据源启动加载;
实体类CompanyDB包含url,username,password等关键信息
实时切换代码 DynamicDataSourceContextHolder.setDataSourceKey(companyId);