您好,登錄后才能下訂單哦!
小編給大家分享一下SpringBoot中AOP方式實(shí)現(xiàn)多數(shù)據(jù)源切換的示例分析,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
設(shè)計(jì)總體思路
Spring-Boot+AOP方式實(shí)現(xiàn)多數(shù)據(jù)源切換,繼承AbstractRoutingDataSource實(shí)現(xiàn)數(shù)據(jù)源動(dòng)態(tài)的獲取,在service層使用注解指定數(shù)據(jù)源。
步驟
一、多數(shù)據(jù)源配置
在application.properties中,我們的配置是這樣的
#主數(shù)據(jù)源 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 #從數(shù)據(jù)源 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數(shù)據(jù)源 --> <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}"/> <!-- 配置獲取連接等待超時(shí)的時(shí)間 --> <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數(shù)據(jù)源 --> <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}"/> <!-- 配置獲取連接等待超時(shí)的時(shí)間 --> <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> <!-- 動(dòng)態(tài)數(shù)據(jù)源,根據(jù)service接口上的注解來(lái)決定取哪個(gè)數(shù)據(jù)源 --> <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事務(wù)管理器 --> <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>
二、動(dòng)態(tài)數(shù)據(jù)源
spring為我們提供了AbstractRoutingDataSource,即帶路由的數(shù)據(jù)源。繼承后我們需要實(shí)現(xiàn)它的determineCurrentLookupKey(),該方法用于自定義實(shí)際數(shù)據(jù)源名稱的路由選擇方法,由于我們將信息保存到了ThreadLocal中,所以只需要從中拿出來(lái)即可。
public class DynamicDataSource extends AbstractRoutingDataSource { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Override protected Object determineCurrentLookupKey() { String dataSource = JdbcContextHolder.getDataSource(); logger.info("數(shù)據(jù)源為{}",dataSource); return dataSource; } }
三. 數(shù)據(jù)源動(dòng)態(tài)切換類
動(dòng)態(tài)數(shù)據(jù)源切換是基于AOP的,所以我們需要聲明一個(gè)AOP切面,并在切面前做數(shù)據(jù)源切換,切面完成后移除數(shù)據(jù)源名稱。
@Aspect @Order(1) //設(shè)置AOP執(zhí)行順序(需要在事務(wù)之前,否則事務(wù)只發(fā)生在默認(rèn)庫(kù)中) @Component public class DataSourceAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); //切點(diǎn) @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();// 獲取目標(biāo)類 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());// 數(shù)據(jù)源放到當(dāng)前線程中 } } catch (Exception e) { logger.error("get datasource error ",e); //默認(rèn)選擇master JdbcContextHolder.putDataSource(DataSourceType.Master.getName());// 數(shù)據(jù)源放到當(dāng)前線程中 } } @AfterReturning("aspect()") public void after(JoinPoint point) { JdbcContextHolder.clearDataSource(); } }
四、數(shù)據(jù)源管理類
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(); } }
五、數(shù)據(jù)源注解和枚舉
我們切換數(shù)據(jù)源時(shí),一般都是在調(diào)用具體接口的方法前實(shí)現(xiàn),所以我們定義一個(gè)方法注解,當(dāng)AOP檢測(cè)到方法上有該注解時(shí),根據(jù)注解中value對(duì)應(yīng)的名稱進(jìn)行切換。
@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; } }
六、切點(diǎn)注解
由于我們的動(dòng)態(tài)數(shù)據(jù)源配置了默認(rèn)庫(kù),所以如果方法是操作默認(rèn)庫(kù)的可以不需要注解,如果要操作非默認(rèn)數(shù)據(jù)源,我們需要在方法上添加@MyDataSource("數(shù)據(jù)源名稱")注解,這樣就可以利用AOP實(shí)現(xiàn)動(dòng)態(tài)切換了
@Component public class xxxServiceImpl { @Resource private XxxMapperExt xxxMapperExt; @MyDataSource(value= DataSourceType.Slave) public List<Object> getAll(){ return xxxMapperExt.getAll(); } }
看完了這篇文章,相信你對(duì)“SpringBoot中AOP方式實(shí)現(xiàn)多數(shù)據(jù)源切換的示例分析”有了一定的了解,如果想了解更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。