自定义注解实现多数据源切换

自定义注解实现多数据源切换

定义注解

/***
 * 数据库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切换当前线程中数据源名称对应数据源。在方法执行后,再将该数据源清除,恢复默认数据源

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值