Spring-Boot + AOP实现多数据源动态切换


声明:本文转载自https://my.oschina.net/stephenzhang/blog/1786156,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据库的性能,考虑把这个查询走从库。所以涉及到需要在一个项目中配置多数据源,并且能够动态切换。

设计总体思路

Spring-Boot+AOP方式实现多数据源切换,继承AbstractRoutingDataSource实现数据源动态的获取,在service层使用注解指定数据源。

步骤

一、多数据源配置

在application.properties中,我们的配置是这样的


 	#主数据源 	druid.master.url=jdbc:mysql://url/masterdb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull 	druid.master.username=xxx 	druid.master.password=123 	druid.master.driver-class-name=com.mysql.jdbc.Driver 	druid.master.max-wait=5000 	druid.master.max-active=100 	druid.master.test-on-borrow=true 	druid.master.validation-query=SELECT 1  	#从数据源 	druid.slave.url=jdbc:mysql://url/slavedb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull 	druid.slave.username=xxx 	druid.slave.password=123 	druid.slave.driver-class-name=com.mysql.jdbc.Driver 	druid.slave.max-wait=5000 	druid.slave.max-active=100 	druid.slave.test-on-borrow=true 	druid.slave.validation-query=SELECT 1 

读取配置

	<!-- master数据源 --> 	<bean primary="true" id="masterdb" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 		<!-- 基本属性 url、user、password --> 		<property name="driverClassName" value="com.mysql.jdbc.Driver"/> 		<property name="url" value="${druid.master.url}"/> 		<property name="username" value="${druid.master.username}"/> 		<property name="password" value="${druid.master.password}"/> 		<!-- 配置初始化最大 --> 		<property name="maxActive" value="${druid.master.max-active}"/> 		<!-- 配置获取连接等待超时的时间 --> 		<property name="maxWait" value="${druid.master.max-wait}"/> 		<property name="validationQuery" value="${druid.master.validation-query}"/> 		<property name="testOnBorrow" value="${druid.master.test-on-borrow}"/>  	</bean>      <!-- slave数据源 --> 	<bean primary="true" id="slavedb" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 		<!-- 基本属性 url、user、password --> 		<property name="driverClassName" value="com.mysql.jdbc.Driver"/> 		<property name="url" value="${druid.slave.url}"/> 		<property name="username" value="${druid.slave.username}"/> 		<property name="password" value="${druid.slave.password}"/>  		<!-- 配置初始化大小、最小、最大 --> 		<property name="maxActive" value="${druid.slave.max-active}"/> 		<!-- 配置获取连接等待超时的时间 --> 		<property name="maxWait" value="${druid.slave.max-wait}"/> 		<property name="validationQuery" value="${druid.slave.validation-query}"/> 		<property name="testOnBorrow" value="${druid.slave.test-on-borrow}"/> 	</bean>      <!-- 动态数据源,根据service接口上的注解来决定取哪个数据源 -->     <bean id="dataSource" class="datasource.DynamicDataSource">         <property name="targetDataSources">             <map key-type="java.lang.String">                 <entry key="slave" value-ref="slavedb"/>                 <entry key="master" value-ref="masterdb"/>             </map>         </property>         <property name="defaultTargetDataSource" ref="masterdb"/>     </bean>  	<!-- Spring JdbcTemplate --> 	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 		<property name="dataSource" ref="dataSource" /> 	</bean>  	<!-- Spring事务管理器 --> 	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 		<property name="dataSource" ref="dataSource" /> 	</bean>  	<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> 		<property name="transactionManager" ref="transactionManager"/> 	</bean>  	<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" order="2" />  	<!-- depositdbSqlSessionFactory --> 	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 		<property name="dataSource" ref="dataSource" /> 		<property name="mapperLocations" value="classpath*:mapper-xxdb/*Mapper*.xml" /> 	</bean>  	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 		<property name="basePackage" value="xxdb.mapper"/> 		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> 	</bean> 

二. 数据源动态切换类

动态数据源切换是基于AOP的,所以我们需要声明一个AOP切面,并在切面前做数据源切换,切面完成后移除数据源名称。

	@Order(1)   //设置AOP执行顺序(需要在事务之前,否则事务只发生在默认库中)         @Aspect 	@Component 	public class DataSourceAspect {  	    private Logger logger = LoggerFactory.getLogger(this.getClass()); 	    //切点 	    @Pointcut("execution(* com.xxx.service.*.*(..))") 	    public void aspect() { } 	 	    @Before("aspect()") 	    private void before(JoinPoint point) { 	        Object target = point.getTarget(); 	        String method = point.getSignature().getName(); 	        Class<?> classz = target.getClass();// 获取目标类 	        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()) 	                .getMethod().getParameterTypes(); 	        try { 	            Method m = classz.getMethod(method, parameterTypes); 	            if (m != null && m.isAnnotationPresent(MyDataSource.class)) { 	                MyDataSource data = m.getAnnotation(MyDataSource.class); 	                logger.info("method :{},datasource:{}",m.getName() ,data.value().getName()); 	                JdbcContextHolder.putDataSource(data.value().getName());// 数据源放到当前线程中 	            } 	        } catch (Exception e) { 	            logger.error("get datasource error ",e); 	            //默认选择master 	            JdbcContextHolder.putDataSource(DataSourceType.Master.getName());// 数据源放到当前线程中 	        } 	 	    } 	 	    @AfterReturning("aspect()") 	    public void after(JoinPoint point) { 	        JdbcContextHolder.clearDataSource(); 	    } 	} 

三、数据源管理类

	public class JdbcContextHolder { 	 	    private final static ThreadLocal<String> local = new ThreadLocal<>(); 	 	    public static void putDataSource(String name) { 	        local.set(name); 	    } 	 	    public static String getDataSource() { 	        return local.get(); 	    } 	 	    public static void clearDataSource() { 	        local.remove(); 	    } 	} 

四、动态数据源

spring为我们提供了AbstractRoutingDataSource,即带路由的数据源。继承后我们需要实现它的determineCurrentLookupKey(),该方法用于自定义实际数据源名称的路由选择方法,由于我们将信息保存到了ThreadLocal中,所以只需要从中拿出来即可。

	public class DynamicDataSource extends AbstractRoutingDataSource { 	 	    private Logger logger = LoggerFactory.getLogger(this.getClass()); 	 	    @Override 	    protected Object determineCurrentLookupKey() { 	        String dataSource = JdbcContextHolder.getDataSource(); 	        logger.info("数据源为{}",dataSource); 	        return dataSource; 	    } 	 	} 

五、数据源注解和枚举

我们切换数据源时,一般都是在调用具体接口的方法前实现,所以我们定义一个方法注解,当AOP检测到方法上有该注解时,根据注解中value对应的名称进行切换。

	@Retention(RetentionPolicy.RUNTIME) 	@Target(ElementType.METHOD) 	public @interface MyDataSource { 	 	    DataSourceType value(); 	 	} 

	public enum  DataSourceType { 	    // 主表 	    Master("master"), 	    // 从表 	    Slave("slave"); 	 	    private String name; 	 	    private DataSourceType(String name) { 	        this.name = name; 	    } 	 	    public String getName() { 	        return name; 	    } 	 	    public void setName(String name) { 	        this.name = name; 	    } 	} 

六、切点注解

由于我们的动态数据源配置了默认库,所以如果方法是操作默认库的可以不需要注解,如果要操作非默认数据源,我们需要在方法上添加@MyDataSource("数据源名称")注解,这样就可以利用AOP实现动态切换了

	@Component 	public class xxxServiceImpl { 	    @Resource 	    private XxxMapperExt xxxMapperExt; 	 	    @MyDataSource(value= DataSourceType.Slave) 	    public List<Object> getAll(){ 	        return xxxMapperExt.getAll(); 	    }  	} 

本文发表于2018年03月28日 22:38
(c)注:本文转载自https://my.oschina.net/stephenzhang/blog/1786156,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1739 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1