spring+hibernate應用層讀寫分離方案,是基於AbstractRoutingDataSource和AOP實現的。其中AbstractRoutingDataSource用於管理數據源並且根據key返回相應的數據源,AOP決定了什麼時候使用什麼數據源的key。
1、相關類的代碼實現:
1)DataSource 註解類,用來標註某個方法使用的數據源,不標註則使用默認的
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
/**
* key="read" 讀庫(從),key="write" 寫庫(主)
* @return
*/
public String key();
}
2)DynamicDataSource 類重寫determineCurrentLookupKey方法,返回數據源的key
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSource();
}
}
3)DynamicDataSourceHolder 類用於線程安全的保存數據源的key
public class DynamicDataSourceHolder {
private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<>();
public static void setDataSource(String dataSourceKey) {
dataSourceHolder.set(dataSourceKey);
}
public static String getDataSource() {
return dataSourceHolder.get();
}
public static void clearDataSource() {
dataSourceHolder.remove();
}
}
4)DynamicDataSourceAop ,設置攔截的方法層面,下面是攔截在service層,當該service執行之前,通過判斷方法有沒有DataSource註解,有則根據DataSource中的key設置數據源的key;service方法執行完畢清除數據源key的設置。
service層使用DataSource註解的原則是:當service裏面有寫方法時就不需要設置,默認使用寫庫,此時讀寫方法都在寫庫中完成;當service裏面沒有寫方法時就使用DataSource設置讀庫,讀方法在讀庫中完成。
@Component
@Aspect
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DynamicDataSourceAop {
/**
* service層方法執行前選擇數據源
* @param point
*/
@Before("execution(* com.test.service..*.*(..))")
public void before(JoinPoint point) {
Object target = point.getTarget();
String methodName = point.getSignature().getName();
Class clazz = target.getClass();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
try {
Method method = clazz.getMethod(methodName, parameterTypes);
if (method != null && method.isAnnotationPresent(DataSource.class)) {
DataSource data = method.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(data.key());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* service層方法執行完後清空數據源選擇,當找不到相應的key的數據源會使用默認的數據源
* @param point
*/
@After("execution(* com.test.service..*.*(..))")
public void after(JoinPoint point) {
DynamicDataSourceHolder.clearDataSource();
}
}
2、spring配置文件
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${masterconnection.url}" />
<property name="username" value="${masterconnection.username}" />
<property name="password" value="${masterconnection.password}" />
</bean>
<bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${slaveconnection.url}" />
<property name="username" value="${slaveconnection.username}" />
<property name="password" value="${slaveconnection.password}" />
</bean>
<!-- 自定義動態數據源 -->
<bean id="dataSource" class="com.test.dao.datasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- 配置讀寫數據源 -->
<entry value-ref="masterDataSource" key="write"></entry>
<entry value-ref="slaveDataSource" key="read"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource"></property>
</bean>
<!-- classpath是指 WEB-INF文件夾下的classes目錄, classpath*:不僅包含class路徑,還包括jar文件中(class路徑)進行查找 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" >
<property name="dataSource" ref="dataSource"/>
<property name="mappingLocations" value="classpath:com/test/dao/hbm/*.hbm.xml"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
</bean>
<!-- 開啓註解事務 只對當前配置文件有效 -->
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>