Spring-Boot快速集成MyBatis动态数据源(AbstractRoutingDataSource)
在一些应用场景下,我们会使用到多个数据源,可能存在不同类型数据库,mysql和sql server之类的同时使用,还有就是主从数据库,读写分离也可能会使用多数据源来解决问题。使用的场景一般有:1,主从数据库切换;2,多租户间数据逻辑隔离
依赖引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
配置动态数据源
/**
* 动态数据源配置
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 数据源存储用map
*/
private final Map<Object,Object> dataSourceMap = new ConcurrentHashMap<>();
/**
* 当前线程数据源key
*/
private final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
@Override
protected Object determineCurrentLookupKey() {
//弹出当前数据源key
return contextHolder.get();
}
/**
* 动态数据源构造方法
* @param defaultDataSource
*/
public DynamicDataSource(DataSource defaultDataSource) {
//存入主数据源
dataSourceMap.put("master",defaultDataSource);
//设定目标数据源map
setTargetDataSources(dataSourceMap);
//设定默认数据源
setDefaultTargetDataSource(defaultDataSource);
DynamicDataSourceUtil.dynamicDataSource =this;
}
}
代码中的dataSourceMap 是用来保存加入动态数据源中数据源,contextHolder 使用ThreadLocal 来定义,保证只为当前线程所持有。定义动态数据源一定要继承AbstractRoutingDataSource 类,并且重写determineCurrentLookupKey方法,determineCurrentLookupKey方法的返回值表示的是当前数据源的key,通过key去找已经加入map中的数据源。
注入数据源
/**
* 数据源设定
*
* @author DavidLei
*/
@Configuration
@MapperScan(basePackages = {"club.dlblog.datasource.mapper"},
sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {
/**
* 数据源设定
*/
@Bean(name = "dataSource")
@ConfigurationProperties("spring.datasource")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
/**
* session工厂
*/
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(
@Qualifier("dynamicDataSource") DynamicDataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().
getResources("classpath:mapper/**/*.xml"));
return sessionFactoryBean.getObject();
}
/**
* session模板
* @param sqlSessionFactory
* @return
*/
@Bean(name = "sqlSessionTemplate")
public SqlSessionTemplate ComSqlSessionTemplate(
@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
/**
* 动态数据源
* @param dataSource
* @return
*/
@Bean(name = "dynamicDataSource")
public DynamicDataSource dynamicDataSource(@Qualifier("dataSource") DataSource dataSource){
return new DynamicDataSource(dataSource);
}
}
在配置类中初始化动态数据源,并将将动态数据源绑定到session工厂上。
数据源管理工具类
/**
* 数据源切换工具
*/
public class DynamicDataSourceUtil {
/**
* 动态数据源
*/
public static DynamicDataSource dynamicDataSource = null;
/**
* 判断当前数据源是否存在
*
* @param tenantId
* @return
*/
public static boolean isExistDataSource(String tenantId) {
if (dynamicDataSource != null) {
return dynamicDataSource.getDataSourceMap().containsKey(tenantId);
} else {
return false;
}
}
/**
* 切换数据源
*
* @param key
*/
public static void setDataSourceKey(String key) {
if (dynamicDataSource != null) {
dynamicDataSource.getContextHolder().set(key);
dynamicDataSource.afterPropertiesSet();
}
}
/**
* 获取当前数据源
*
* @return
*/
public static String getDataSourceKey() {
if (dynamicDataSource != null) {
return dynamicDataSource.getContextHolder().get();
} else {
return null;
}
}
/**
* 切换到默认数据源
*/
public static void clearDataSourceKey() {
if (dynamicDataSource != null) {
dynamicDataSource.getContextHolder().remove();
}
}
/**
* 根据租户ID设置数据源
*
* @param datatBase
* @return
*/
public static void addDataSource(String datatBase, DataSource dataSource) {
if (dynamicDataSource == null) {
return;
}
// 没有数据源时添加数据源,有数据源直接使用
if (!isExistDataSource(datatBase)) {
// 新增数据源
dynamicDataSource.getDataSourceMap().put(datatBase, dataSource);
}
// 切换数据源
checkoutDataSource(datatBase);
}
/**
* 切换数据源
*
* @param tenantId
*/
public static void checkoutDataSource(String tenantId) {
if (dynamicDataSource != null) {
// 切换数据源
setDataSourceKey(tenantId);
dynamicDataSource.afterPropertiesSet();
}
}
/**
* 创建数据源
* @param driverClassName
* @param url
* @param userName
* @param password
* @return
*/
public static DataSource createDataSource(String driverClassName,String url,
String userName,String password){
return DataSourceBuilder.create().driverClassName(driverClassName)
.url(url).username(userName).password(password).build();
}
}
在工具类中就是对动态数据源中的存储数据源的map进行put和remove操作,指定数据源时只需将contextHolder重新设定。
在进行具体CRUD时,数据源就会根据contextHolder进行切换。createDataSource方法随时可以创建新的数据源,放入动态数据源中进行管理。
拓展用法
定义过滤器,根据请求头来变更数据源
@Order(1)
@WebFilter(urlPatterns = "/*")
public class DynamicDataSourceFilter extends GenericFilter {
private final static String TARGET_ID = "XXX-TARGET-ID";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String targetId = request.getHeader(TARGET_ID);
//数据源切换
DynamicDataSourceUtil.checkoutDataSource(targetId);
filterChain.doFilter(servletRequest,servletResponse);
}
}
git地址 https://github.com/DavidLei08/BlogDataSource.git