(Notice:歡迎交流和溝通雹有,Wx:IT_Ezra忍级,QQ 654303408。僅個人觀點和個人理解有問題討論也可聯(lián)系我邮丰。)
(PS:寫這篇博客的原因是因為剛?cè)胄胁痪昧ち龋谝淮伟炎约旱淖约核鶎W(xué)的東西應(yīng)用在實際層面,更讓我意識到柠座,基礎(chǔ)的重要性邑雅。)
使用場景:當(dāng)我們的生產(chǎn)數(shù)據(jù)在逐漸增多,數(shù)據(jù)庫分庫分表技能也變得很常見妈经。那么假如說淮野,我們也用戶表放到DB1,把訂單表放到DB2…等吹泡。那么我們查詢的時候骤星,要根據(jù)不同的數(shù)據(jù)庫去查不同的數(shù)據(jù)。眾所周知爆哑,通常一個Mybatis-config.xml里面是配置一個datasource洞难,但是實際上是我們可以配置到多個datasource,然后通過spring-aop來實現(xiàn)揭朝。下面直接上干貨队贱。
首先我們要配置mybatis的xml配置文件色冀,當(dāng)然有不規(guī)范的寫法,就是把其中的內(nèi)容直接放到spring的beans.xml文件下也是能夠?qū)崿F(xiàn)的柱嫌。
1.datasource的配置锋恬,配置多個datasource,根據(jù)自己的實際情況而定编丘。
2.設(shè)置多數(shù)據(jù)源路由与学。實際上就是一個分發(fā)器,就是通過到時候切面的切點值來選擇不同的數(shù)據(jù)庫
3 JDBC事務(wù)管理嘉抓。配置jdbc的事務(wù)索守。spring里面也有事務(wù)注解來實現(xiàn)DB的事務(wù)。
4 配置sessionFactory 抑片,以及包掃描
5 然后兩個工具類以及aop的實現(xiàn)卵佛。
切面接口,DataSourceAspect 蓝丙,該接口是在service層。也可以實現(xiàn)controller層切
/**
- @ author ezra
- @ date 2019/7/5 11:27
/
@Slf4j
@Aspect
@Component
@Order(1) //請注意:這里order一定要小于tx:annotation-driven的order望拖,即先執(zhí)行DataSourceAspect切面渺尘,再執(zhí)行事務(wù)切面,才能獲取到最終的數(shù)據(jù)源
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {
/*
* 切入點 service包及子孫包下的所有類 也可以改為controller級別下的所有
*/
@Pointcut("execution(* com.dfs.service..*.*(..))")
public void aspect() {
}
/**
* 配置前置通知,使用在方法aspect()上注冊的切入點
*/
@Before("aspect()")
public void before(JoinPoint point) {
Class<?> target = point.getTarget().getClass();
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod() ;
DataSource dataSource = null ;
//從類初始化
dataSource = this.getDataSource(target, method) ;
//從接口初始化
if(dataSource == null){
for (Class<?> clazz : target.getInterfaces()) {
dataSource = getDataSource(clazz, method);
if(dataSource != null){
break ;//從某個接口中一旦發(fā)現(xiàn)注解说敏,不再循環(huán)
}
}
}
if(dataSource != null && !StringUtils.isEmpty(dataSource.value()) ){
HandleDataSource.setDataSource(dataSource.value());
}
}
@After("aspect()")
public void after(JoinPoint point) {
//使用完記得清空
HandleDataSource.setDataSource(null);
}
/**
* 獲取方法或類的注解對象DataSource
* @param target 類class
* @param method 方法
* @return DataSource
*/
public DataSource getDataSource(Class<?> target, Method method){
try {
//1.優(yōu)先方法注解
Class<?>[] types = method.getParameterTypes();
Method m = target.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
return m.getAnnotation(DataSource.class);
}
//2.其次類注解
if (target.isAnnotationPresent(DataSource.class)) {
return target.getAnnotation(DataSource.class);
}
} catch (Exception e) {
e.printStackTrace();
log.error(MessageFormat.format("通過注解切換數(shù)據(jù)源時發(fā)生異常[class={0},method={1}]:"
, target.getName(), method.getName()),e) ;
}
return null ;
}
}
DataSource 注解接口
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value();
}
DataSourceRouter 工具類
public class DataSourceRouter extends AbstractRoutingDataSource {
// 獲取數(shù)據(jù)源名稱
protected Object determineCurrentLookupKey() {
return HandleDataSource.getDataSource();
}
}
HandleDataSource 工具類
public class HandleDataSource {
// 數(shù)據(jù)源名稱線程池
private static final ThreadLocal<String> holder = new ThreadLocal<String>();
/**
* 設(shè)置數(shù)據(jù)源
* @param datasource 數(shù)據(jù)源名稱
/
public static void setDataSource(String datasource) {
holder.set(datasource);
}
/*
* 獲取數(shù)據(jù)源
* @return 數(shù)據(jù)源名稱
/
public static String getDataSource() {
return holder.get();
}
/*
* 清空數(shù)據(jù)源
*/
public static void clearDataSource() {
holder.remove();
}
}