Java實現讀寫分離
一前言
在網際網路專案中,隨著業務的增加,訪問的增長,對系統性能,擴充套件性,伸縮性提出更高的同時,資料庫的壓力也陡然增加。越來越多的系統採用分散式系統架構,在資料方面,也採用資料庫叢集,與此同時,基於系統訪問,資料的查詢較多,增刪改較少,為了減少資料壓力,提高系統響效率,採用資料庫讀寫分離也是一個較好的選擇。採用此種策略,不僅使提高系統的響應時效,而且利於資料庫的擴充套件。
二 理論
java的形式的讀寫分離有兩種:
1.配置多個數據源,根據業務需求訪問不同的資料,指定對應的策略
由於此方法相對易懂,簡單,不做過多介紹。
2.動態切換資料來源,根據配置的檔案,業務動態切換訪問的資料庫,此方案通過Spring的AOP,AspactJ來實現動態織入,通過程式設計繼承實現Spring中的AbstractRoutingDataSource,來實現資料庫訪問的動態切換,不僅可以方便擴充套件,不影響現有程式,而且對於此功能的增刪也比較容易。下面做詳細介紹。
a.首先AbstractRoutingDataSource繼承自Spirng的AbstractDataSource,AbstractDataSource實現了java.sql的DataSource資料介面。
DataSource有如下兩個方法:下述方法獲取資料庫連線。
//獲取資料庫連線
Connection getConnection() throws SQLException;
//需通過安全認證,獲取資料庫連線
Connection getConnection(String username, String password) throws SQLException;
AbstractDataSource實現上述方法如下:
//通過determineTargetDataSource()方法獲取的例項獲取資料庫連線。
public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); }
//通過determineTargetDataSource()方法獲取的例項獲取需要安全認證的資料庫連線。public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); } //獲取資料來源
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
//獲取對應資料來源的屬性鍵(因resolvedDataSources是map資料結構)
Object lookupKey = determineCurrentLookupKey();
//根據該引數獲取資料來源(因resolvedDataSources是map資料結構) DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; }
b.編寫一個工具類繼承AbstractRoutingDataSource來動態獲取資料來源。
public class DataSourceTool extends AbstractRoutingDataSource {
//多個副本,實現多執行緒的資源共享
public static final ThreadLocal<String> holder = new ThreadLocal<String>();
@Override
protected Object determineCurrentLookupKey() {
return DataSourceTool .getDataSouce();
}
public static void putDataSource(String name) {
holder.set(name);
}
public static String getDataSouce() {
return holder.get();
}
}
c.通過AspactJ實現動態織入程式碼,實現程式碼的無浸入新增。
public class DataSourceAspectJ {
//在方法呼叫之前進行切面操作
public void before(JoinPoint point)
{ //獲取目標物件
Object target = point.getTarget();
//獲取方法簽名信息:然後獲取於方法名
String method = point.getSignature().getName();
//獲取目標物件類實現的介面
Class<?>[] classz = target.getClass().getInterfaces();
//獲取該方法簽名對應方法的引數型別
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
//獲取目標物件的方法
Method m = classz[0].getMethod(method, parameterTypes);
//若方法不為空,且方法上的註解是DataSource
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource data = m
.getAnnotation(DataSource.class);
DynamicDataSourceHolder.putDataSource(data.value());
System.out.println(data.value());
}
} catch (Exception e) {
e.printStrace();
}
}
}
d.Spring檔案配置:
<!--資料來源配置,主從庫配置 -->
<bean id="masterdataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/master" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<bean id="slavedataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/slave" />
<property name="username" value="root" />
<property name="password" value="root" />
</bean>
<beans:bean id="dataSource" class="com.myd.cn.db.DataSourceTool ">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- write -->
<entry key="master" value-ref="masterdataSource"/>
<!-- read -->
<entry key="slave" value-ref="slavedataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="masterdataSource"/>
</beans:bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:config/mybatis-config.xml" />
</bean>
<!--AspactJ配置 -->
<!--AspactJ動態代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<beans:bean id="manyDataSourceAspect" class="com.myd.cn.proxy.DataSourceAspectJ" />
<aop:config>
<aop:aspect id="c" ref="manyDataSourceAspect">
<aop:pointcut id="tx" expression="execution(* com.myd.cn.mapper.*.*(..))"/>
<aop:before pointcut-ref="tx" method="before"/>
</aop:aspect>
</aop:config>
e:以下是MyBatis的UserMapper的定義
public interface UserMapper {
@DataSource("master")
public void add(User user);
@DataSource("master")
public void update(User user);
@DataSource("master")
public void delete(int id);
@DataSource("slave")
public User loadbyid(int id);
@DataSource("master")
public User loadbyname(String name);
@DataSource("slave")
public List<User> list();
}
三 總結
總的來說,AspectJ實現資料來源的動態代理是比較方便,在不需要切換資料來源的情況,去除切換程式碼不影響其他功能,同時易於程式擴充套件。