自定义注解实现多数据源切换
定义注解
/***
* 数据库annotation定义
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceTarget {
String value() default DataSourceTarget.PAY;
public static String PAY = "payDataSource";
public static String BUZ = "buzDataSource";
public static String ACCOUNT = "accountDataSource";
}
application.properties配置数据源
dataSource.driverClassName = com.mysql.jdbc.Driver
# 初始化连接
dataSource.initialSize = 10
# 最大连接数量
dataSource.maxActive = 100
# 最大空闲连接
dataSource.maxIdle = 10
# 最小空闲连接
dataSource.minIdle = 2
# 连接有效性检查
dataSource.testOnBorrow = false
dataSource.testWhileIdle = true
dataSource.validationQuery = select 1
#pay数据源--finance_pay_v2
pay.dataSource.url = jdbc:mysql://r1.fdb.local/finance_pay_v2?useUnicode=true&characterEncoding=utf-8
pay.dataSource.username = fin_pay_v2
pay.dataSource.password = ******
#buz数据源--finance_buz_v2
buz.dataSource.url = jdbc:mysql://r1.fdb.local/finance_buz_v2?useUnicode=true&characterEncoding=utf-8
buz.dataSource.username = fin_buz_v2
buz.dataSource.password = ******
#account数据源--finance_account_v2
account.dataSource.url = jdbc:mysql://r1.fdb.local/finance_account_v2?useUnicode=true&characterEncoding=utf-8
account.dataSource.username = fin_account_v2
account.dataSource.password = ******
继承AbstractRoutingDataSource
SpringBoot提供AbstractRoutingDataSource类让用户根据自己定义的规则选取当前数据源,实现可动态切换的数据源。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。AbstractRoutingDataSource的getConnection() 方法根据查找 lookup key 键对不同目标数据源的调用,通常是通过(但不一定)某些线程绑定的事物上下文来实现。
public class DynamicDataSource extends AbstractRoutingDataSource
{
/**
* 用户返回当且切换到的数据库
*/
@Override
protected Object determineCurrentLookupKey()
{
//DynamicDataSourceHolder有获取和设置当前数据库的方法get & put
return DynamicDataSourceHolder.getDataSource();
}
}
使用当前线程操作数据源对应名称
public class DynamicDataSourceHolder {
public static final ThreadLocal<String> HOLDER = new ThreadLocal<String>();
// 将数据源对应的名称放入当前线程
public static void putDataSource(String name) {
HOLDER.set(name);
}
// 将数据源对应的名称从当前线程取出
public static String getDataSource() {
return HOLDER.get();
}
// 清除
public static void clearCustomerType() {
HOLDER.remove();
}
}
读取数据源配置
实现EnvironmentAware接口,重写setEnvironment方法,在工程启动的时候可以获得application.properties配置文件中配置的属性值
@Component
public class DataBaseConfig implements EnvironmentAware{
private Environment environment;
@Override
public void setEnvironment(Environment environment){
this.environment = environment;
}
/**
* 读取数据源配置
*/
private DataSource payDataSource() throws Exception {
Properties p =new Properties();
p.put("driverClassName",environment.getRequiredProperty("dataSource.driverClassName"));
p.put("url",environment.getRequiredProperty("pay.dataSource.url"));
p.put("username",environment.getRequiredProperty("pay.dataSource.username"));
p.put("password",environment.getRequiredProperty("pay.dataSource.password"));
p.put("initialSize",environment.getRequiredProperty("dataSource.initialSize"));
p.put("maxActive",environment.getRequiredProperty("dataSource.maxActive"));
p.put("maxIdle",environment.getRequiredProperty("dataSource.maxIdle"));
p.put("minIdle",environment.getRequiredProperty("dataSource.minIdle"));
p.put("testOnBorrow",environment.getRequiredProperty("dataSource.testOnBorrow"));
p.put("testWhileIdle",environment.getRequiredProperty("dataSource.testWhileIdle"));
p.put("validationQuery",environment.getRequiredProperty("dataSource.validationQuery"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(p);
return dataSource;
}
private DataSource buzDataSource() throws Exception{
Properties p =new Properties();
p.put("driverClassName",environment.getRequiredProperty("dataSource.driverClassName"));
p.put("url",environment.getRequiredProperty("buz.dataSource.url"));
p.put("username",environment.getRequiredProperty("buz.dataSource.username"));
p.put("password",environment.getRequiredProperty("buz.dataSource.password"));
p.put("initialSize",environment.getRequiredProperty("dataSource.initialSize"));
p.put("maxActive",environment.getRequiredProperty("dataSource.maxActive"));
p.put("maxIdle",environment.getRequiredProperty("dataSource.maxIdle"));
p.put("minIdle",environment.getRequiredProperty("dataSource.minIdle"));
p.put("testOnBorrow",environment.getRequiredProperty("dataSource.testOnBorrow"));
p.put("testWhileIdle",environment.getRequiredProperty("dataSource.testWhileIdle"));
p.put("validationQuery",environment.getRequiredProperty("dataSource.validationQuery"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(p);
return dataSource;
}
private DataSource accountDataSource() throws Exception{
Properties p =new Properties();
p.put("driverClassName",environment.getRequiredProperty("dataSource.driverClassName"));
p.put("url",environment.getRequiredProperty("account.dataSource.url"));
p.put("username",environment.getRequiredProperty("account.dataSource.username"));
p.put("password",environment.getRequiredProperty("account.dataSource.password"));
p.put("initialSize",environment.getRequiredProperty("dataSource.initialSize"));
p.put("maxActive",environment.getRequiredProperty("dataSource.maxActive"));
p.put("maxIdle",environment.getRequiredProperty("dataSource.maxIdle"));
p.put("minIdle",environment.getRequiredProperty("dataSource.minIdle"));
p.put("testOnBorrow",environment.getRequiredProperty("dataSource.testOnBorrow"));
p.put("testWhileIdle",environment.getRequiredProperty("dataSource.testWhileIdle"));
p.put("validationQuery",environment.getRequiredProperty("dataSource.validationQuery"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(p);
return dataSource;
}
@Bean(name = "dataSource")
public DynamicDataSource dataSource() throws Exception
{
DataSource pay = payDataSource();
DataSource buz = buzDataSource();
DataSource account = accountDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceTarget.PAY, pay);
targetDataSources.put(DataSourceTarget.BUZ, buz);
targetDataSources.put(DataSourceTarget.ACCOUNT, account);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);
dataSource.setDefaultTargetDataSource(pay);
return dataSource;
}
@Bean(name = "sessionFactory")
public SqlSessionFactory sqlSessionFactory(AbstractRoutingDataSource dynamicDataSource) throws Exception
{
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
Properties configurationProperties = new Properties();
configurationProperties.put("cacheEnabled", true);
configurationProperties.put("lazyLoadingEnabled", true);
configurationProperties.put("aggressiveLazyLoading", false);
configurationProperties.put("multipleResultSetsEnabled", true);
configurationProperties.put("useColumnLabel", true);
configurationProperties.put("useGeneratedKeys", true);
configurationProperties.put("autoMappingBehavior", "FULL");
configurationProperties.put("defaultExecutorType", "BATCH");
configurationProperties.put("defaultStatementTimeout", 25000);
sqlSessionFactoryBean.setConfigurationProperties(configurationProperties);
// 设置动态数据源
sqlSessionFactoryBean.setDataSource(dynamicDataSource);
// 设置mybatis的配置文件路径
sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
// 设置mapper文件路径
PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(pathMatchingResourcePatternResolver
.getResources("classpath*:mybatis/mapper/*-mapper.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean(name = "sessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sessionFactory)
{
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sessionFactory);
return sqlSessionTemplate;
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(AbstractRoutingDataSource dynamicDataSource)
{
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dynamicDataSource);
return dataSourceTransactionManager;
}
}
切面
@Aspect
@Component
public class DataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
@Pointcut("execution(* com.*.*.multi.base.*.*.*(..))")
private void aspectPoint() {
// 定义一个切入点
}
@Before("aspectPoint()")
public void doBefore(JoinPoint point) {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class<?>[] classz = target.getClass().getInterfaces();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
Method m = null;
try {
m = classz[0].getMethod(method, parameterTypes);
logger.info("当前方法:" + m.getName() + "类名称:" + classz[0].getName());
} catch (Exception e) {
logger.error(e.getMessage());
return;
}
if (m != null && m.isAnnotationPresent(DataSourceTarget.class)) {
DataSourceTarget data = m.getAnnotation(DataSourceTarget.class);// 获取访问mapper中的注释
DynamicDataSourceHolder.putDataSource(data.value());// 获取注释中的value值,确定访问的数据源
logger.info("当前数据源---" + data.value());
} else {
logger.info("当前数据源---主数据源");
}
}
@After("aspectPoint()")
public void doAfter(JoinPoint point) {
// 清除数据源配置
DynamicDataSourceHolder.clearCustomerType();
}
}
总结
总体流程就是,工程启动时,将所有使用的数据源以map形式加载到AbstractRoutingDataSource的子类targetDataSources属性中,在方法执行前,由切面检查当前类或者方法上有无自定义注解,如果有,则将注解对应名设置到当前线程中,由AbstractRoutingDataSource切换当前线程中数据源名称对应数据源。在方法执行后,再将该数据源清除,恢复默认数据源