從零開始學 Java - Spring AOP 實現主從讀寫分離

KathrinTodd 8年前發布 | 16K 次閱讀 AOP Spring Java開發

深刻討論為什么要讀寫分離?

為了服務器承載更多的用戶?提升了網站的響應速度?分攤數據庫服務器的壓力?就是為了雙機熱備又不想浪費備份服務器?上面這些回答,我認為都不是錯誤的,但也都不是完全正確的。「讀寫分離」并不是多么神奇的東西,也帶不來多么大的性能提升,也許更多的作用的就是數據安全的備份吧。

從一個庫到讀寫分離,從理論上對服務器壓力來說是會帶來一倍的性能提升,但你仔細思考一下,你的應用服務器真的很需要這一倍的提升么?那倒不如你去試著在服務器使用一下緩存系統,如 Memcached、Redis 這些分布式緩存,那性能可能是幾十倍的提升。而且,在服務器硬件異常強悍及性能廉價的今天,完全更沒必要了,所以,在今天,我認為它更多的職責就是為了數據安全而設計的,同時又提升了一些性能,這樣也挺好。

可能我們更應該稱之為 主從分離 。

利用 AOP 實現讀寫分離

讀寫分離方式很簡單,就是在你讀數據是去連接 從庫 ,在你寫數據的時候去連接 主庫 ,具體代碼實現當然就是連接時候去操作了,這沒什么難度,在代碼里寫就是了。可是,有追求的程序猿都是不是這么解決問題的呢!

其實通過上篇的 從零開始學 Java - Spring AOP 攔截器的基本實現 我們知道 AOP 可以實現在方法開始執行前后插入執行我們想要的代碼,那這樣,我們是不是可以在 執行數據庫操作前根據業務來動態切換數據源 呢?

思考一下這個方式理論上好像是可行的,這種方式首先不需要在業務代碼中去做切換,二是可能以后我們不需要讀寫分離了,把 AOP 切換的代碼去掉就行了,三是可能就是拓展性好了。

等不了了,開始擼代碼

你可能想深入的了解的話,我這里給你幾個程序里用到的關鍵字 enum(枚舉) 、 annotation(自定義注解) 、 JoinPoint(注入點) 、 AbstractRoutingDataSource(數據源接口子類) ,你理解了這些就知道了,其實你并不需要深入某些深層的東西,了解下即可。

一、建立 JdbcContextHolder.java 類

public class JdbcContextHolder {

private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

public static void setJdbcType(String jdbcType) {
    contextHolder.set(jdbcType);
}

public static void setSlave() {
    setJdbcType("slave");
}

public static void setMaster() {
    clearJdbcType();
}

public static String getJdbcType() {
    return (String) contextHolder.get();
}

public static void clearJdbcType() {
    contextHolder.remove();
}

}</code></pre>

這個類的作用就是用來 設置、獲取數據源連接

二、新建 DynamicDataSource.java 類,繼承于 AbstractRoutingDataSource

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import cn.mayongfa.common.JdbcContextHolder;

public class DynamicDataSource extends AbstractRoutingDataSource {

@Override
protected Object determineCurrentLookupKey() {
    // 獲取當前數據源連接
    return JdbcContextHolder.getJdbcType();
}

}</code></pre>

通過研究,我們知道 determineCurrentLookupKey 方法是獲取相關數據源連接的,所以重寫 determineCurrentLookupKey 方法就可以啦,然后我們去通過剛剛我們建立的 JdbcContextHolder 類去獲取。那怎么設置呢?

三、建立數據源 DataSourceType.java 枚舉類

public enum DataSourceType {

//主庫
Master("master"),

//從庫
Slave("slave");

private DataSourceType(String name) {
    this.name = name;
}

private String name;

public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}

}</code></pre>

這個枚舉類的作用其實就是為了設置數據源而生的,它的目的就是讓設置數據源時更方便,如絲般順滑。

四、新建 DataSource.java Annotation(自定義注解)類

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Documented public @interface DataSource {

DataSourceType value() default DataSourceType.Master;

}</code></pre>

自定義注解的意義不再過多討論,一句話來說就是可以讓你 在類或方法名上以打標簽的形式讓該方法變得不一樣 。具體怎么「不一樣」,這個在于你。

五、新建 DataSourceChoose.java 數據庫切換類

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature;

import cn.mayongfa.common.JdbcContextHolder;

public class DataSourceChoose {

//方法執行前 public void before(JoinPoint point){ Object target = point.getTarget();
String method = point.getSignature().getName();
Class<?>[] classz = target.getClass().getInterfaces();
MethodSignature methodSignature = (MethodSignature)point.getSignature(); Class<?>[] parameterTypes = methodSignature.getMethod().getParameterTypes(); try { Method m = classz[0].getMethod(method, parameterTypes);
if (m!=null && m.isAnnotationPresent(DataSource.class)) {
DataSource data = m.getAnnotation(DataSource.class);
JdbcContextHolder.clearJdbcType(); JdbcContextHolder.setJdbcType(data.value().getName()); }
} catch (Exception e) {
// TODO: handle exception
} } }</code></pre>

這個其實是一個攔截器類,主要作用就是攔截那些方法名上有 @DataSource 這個自定義注解的,完了根據獲取注解的 value() 值,來做相應的數據源切換。

 

 

來自:http://blog.mayongfa.cn/87.html

 

 本文由用戶 KathrinTodd 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!