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>