SpringBoot提供了org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
来动态的切换数据源。
可以借助ThreadLocal
为每一个线程来选择合适的数据源。
源码分析:
//可以路由的数据源
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
@Nullable
private Map
代码实现
1. 定义枚举类:
public enum DataSourceAddressEnum {
/**
* 主数据库
*/
MASTER,
/**
* 从数据库
*/
SLAVE;
}
2. 定义注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RoutingDataSource {
/**
* 路由的DataSource地址,默认为MASTER
*/
DataSourceAddressEnum value() default DataSourceAddressEnum.MASTER;
}
3. 定义AOP代理,处理对应的注解:
import lombok.extern.slf4j.Slf4j;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Objects;
@Aspect
@Component
@Order(10000)
@Slf4j
public class RoutingDataSourceAOP {
@Pointcut("@annotation(com.tellme.config.datasource.RoutingDataSource)|| @within(com.tellme.config.datasource.RoutingDataSource)")
public void routingDataSourcePointcut() {
}
@Around("routingDataSourcePointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RoutingDataSource routerDataSource = method.getAnnotation(RoutingDataSource.class);
// 如果没有设置则默认为 MASTER
DataSourceAddressEnum dataSourceAddressEnum = Objects.isNull(routerDataSource) ?
DataSourceAddressEnum.MASTER : routerDataSource.value();
// 通过向ThreadLocal设置对应的key,来选择数据源,达到动态切换数据源的目的。
DataSourceContextHolder.setCurrentDataSource(dataSourceAddressEnum);
try {
return joinPoint.proceed();
} finally {
DataSourceContextHolder.removeDataSource();
}
}
}
配置ThreadLocal:
public class DataSourceContextHolder {
private static final ThreadLocal CONTEXT_HOLDER = ThreadLocal.withInitial(() -> DataSourceAddressEnum.MASTER);
public static void setCurrentDataSource(DataSourceAddressEnum dataSourceAddressEnum) {
CONTEXT_HOLDER.set(dataSourceAddressEnum);
}
public static DataSourceAddressEnum getCurrentDataSource() {
return CONTEXT_HOLDER.get();
}
public static void removeDataSource() {
CONTEXT_HOLDER.remove();
}
}
4. 初始化路由数据源类:
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
@Slf4j
public class RoutingDataSourceWithAddress extends AbstractRoutingDataSource {
/**
* 路由数据源类进行初始化
* @param defaultTargetDataSource 默认的 DataSource
* @param targetDataSources 配置的所有 DataSource
*/
public RoutingDataSourceWithAddress(DataSource defaultTargetDataSource, Map targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
}
/**
*配置的数据源
*/
@Override
protected Object determineCurrentLookupKey() {
//通过ThreadLocal获取到key,来切换数据源
DataSourceAddressEnum routingDataSourceAddressEnum = DataSourceContextHolder.getCurrentDataSource();
if (log.isDebugEnabled()) {
log.debug("routing data source address is {}", routingDataSourceAddressEnum.name());
}
return routingDataSourceAddressEnum;
}
}
5. 数据源初始化:
import com.alibaba.druid.pool.DruidDataSource;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.sql.DataSource;
import java.util.Map;
/**
* 动态配置数据源
**/
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class, DruidDataSource.class})
@EnableConfigurationProperties(MybatisProperties.class)
public class RoutingDataSourceAutoConfiguration {
/**
* 配置master数据源
* 借助DruidDataSource类的配置,配置文件使用spring.datasource.druid.master的前缀。
*/
@Bean(name = "masterDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create(this.getClass().getClassLoader())
.type(com.alibaba.druid.pool.DruidDataSource.class).build();
}
/**
* 配置slave数据源,
* 借助DruidDataSource类的配置,配置文件使用spring.datasource.druid.slave的前缀。
*/
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.slave")
public DataSource slaveDataSource(@Autowired @Qualifier("masterDataSource") DataSource masterDataSource) {
return DataSourceBuilder.create(this.getClass().getClassLoader())
.type(com.alibaba.druid.pool.DruidDataSource.class).build();
}
/**
* 初始化路由DataSource
*/
@Bean
public DataSource dataSource(
@Autowired @Qualifier("masterDataSource") DataSource masterDataSource,
@Autowired @Qualifier("slaveDataSource") DataSource slaveDataSource) {
DataSource defaultTargetDataSource;
Map targetDataSources = ImmutableMap.of(
DataSourceAddressEnum.MASTER, defaultTargetDataSource = masterDataSource,
DataSourceAddressEnum.SLAVE, slaveDataSource);
return new RoutingDataSourceWithAddress(defaultTargetDataSource, targetDataSources);
}
/**
* 使用SqlSessionFactoryBean配置MyBatis的SqlSessionFactory
**/
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(
@Autowired @Qualifier("dataSource") DataSource routingDataSourceWithAddress,
@Autowired MybatisProperties mybatisProperties,
@Autowired ResourceLoader resourceLoader) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(routingDataSourceWithAddress);
// 设置configuration
org.apache.ibatis.session.Configuration configuration = mybatisProperties.getConfiguration();
factory.setConfiguration(configuration);
// 设置SqlSessionFactory属性
String configLocation;
if (StringUtils.isNotBlank(configLocation = mybatisProperties.getConfigLocation())) {
factory.setConfigLocation(resourceLoader.getResource(configLocation));
}
Resource[] resolveMapperLocations;
if (ArrayUtils.isNotEmpty(resolveMapperLocations = mybatisProperties.resolveMapperLocations())) {
factory.setMapperLocations(resolveMapperLocations);
}
String typeHandlersPackage;
if (StringUtils.isNotBlank(typeHandlersPackage = mybatisProperties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(typeHandlersPackage);
}
String typeAliasesPackage;
if (StringUtils.isNotBlank(typeAliasesPackage = mybatisProperties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(typeAliasesPackage);
}
return factory.getObject();
}
/**
* 使用routingDataSourceWithAddress配置数据库事务
*/
@Bean
@ConditionalOnMissingBean
public DataSourceTransactionManager dataSourceTransactionManager(
@Autowired @Qualifier("dataSource") DataSource routingDataSourceWithAddress) {
return new DataSourceTransactionManager(routingDataSourceWithAddress);
}
/**
* 编程式事务
*/
@Bean
public TransactionTemplate transactionTemplate(
@Autowired @Qualifier("dataSourceTransactionManager") PlatformTransactionManager platformTransactionManager) {
return new TransactionTemplate(platformTransactionManager);
}
@Bean
@ConditionalOnMissingBean(RoutingDataSourceAOP.class)
public RoutingDataSourceAOP routingDataSourceAOP() {
return new RoutingDataSourceAOP();
}
}
6. yml的配置
spring:
datasource:
name: mysql_test
type: com.alibaba.druid.pool.DruidDataSource
#druid相关配置
druid:
master:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test_db?allowMultiQueries=true
username: root
password: 123qwe
#初始化连接数
initial-size: 10
#最小活跃连接数
min-size: 5
#最大活跃连接数
max-active: 30
#获取连接的等待时间
max-wait: 60000
slave:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/exam?allowMultiQueries=true
username: root
password: 123qwe
代码实现
@RoutingDataSource(DataSourceAddressEnum.MASTER)
public void noTransactionMethod(UserT userT) {
//查看事务是否失效
TestTransactionService testTransactionService = (TestTransactionService) AopContext.currentProxy();
testTransactionService.transactionMethod(userT);
}