SPRINGAOP实现基于注解的数据源动态切换(转)

需求

代码实现读写数据库分离

武器

spring3.0以上版本

实现思路

1、继承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,自定义数据源路由。

2、实现数据源类型管理工具,诸如DBContextHolder,包含设置和读取当前数据源配置。

3、实现数据源切换的AOP。

4、自定义只读注解,诸如@ReadOnlyKey。

5、配置transactionManager,实现aop。

代码示例

1、自定义的DynamicDataSource

复制代码
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 自动查找数据源 * *
@return 数据源名
*/
@Override
protected Object determineCurrentLookupKey() {
String dataSource
= getDataSource();
return dataSource;
}
}
复制代码

2、数据源类型管理工具DBContextHolder

复制代码
public abstract class DBContextHolder {
    /**
* 数据源类型管理
* <p>
* 考虑多线程,为保证线程之间互不干扰,所以使用ThreadLocal作线程隔离;<br>
* 参数是数据源键值
* </p>
*
*
@see ThreadLocal
*/
private static ThreadLocal<String> contextHolder = new
ThreadLocal<String>();

/**

* 数据库源类型

* <p>

* 配置数据源的时候,请遵守以下约束:<br>

* 读写:dataSourceKeyRW;<br>

* 读:dataSourceKeyR.

* </p>

*/

public enum DbType {

DB_TYPE_RW(
"dataSourceKeyRW"), DB_TYPE_R("dataSourceKeyR");

private String dataSourceKey;

DbType(String dataSourceKey) {

this.dataSourceKey = dataSourceKey;

}

public String getDataSourceKey() {

return dataSourceKey;

}

}

/**

* 获取数据源

* <p>

* 如果未设置,默认返回读数据源

* </p>



*
@return 数据源键值

/

public static String getDataSource() {

String dataSource
= contextHolder.get();

if (StringUtils.isEmpty(dataSource)) {

dataSource
= DbType.DB_TYPE_RW.dataSourceKey;

}

return dataSource;

}

/**
* 设置数据源

*
*
@param dataSourceKey 数据源键值

*/

public static void setDataSource(String dataSourceKey) {

contextHolder.set(dataSourceKey);

}

}

复制代码

注:定义了DbType枚举,分别定义了读和写的数据源键值。


3、实现AOP。


复制代码

public class DataSourceSwitchingAop {
/**
* 设置切点数据源
* <p>
* 调试输出数据源.
* </p>
* *
@param joinPoint 切点
*
@param dataSourceKey 当前数据源键值
*/
private void setDataSourceByKey(JoinPoint joinPoint, String dataSourceKey) {
setDataSource(dataSourceKey);
debugLog(joinPoint.getTarget().getClass().getSimpleName()
+ "." + joinPoint.getSignature().getName() + "配置数据源:" + getDataSource());
}

/**

* 切换数据源

* <p>

* 切换优先级由高到底如下;方法上注解DataSourceKey,方法上注解ReadOnlyKey,类上注解DataSourceKey;<br>

* 如果未注解,则默认设置写数据源.

* </p>

*
*
@param joinPoint 切点

*
@see DataSourceKey

*
@see ReadOnlyKey

*
@see DbType

*/
public void switchDataSource(JoinPoint joinPoint) {

Class
<?> targetClass = joinPoint.getTarget().getClass();

String methodName
= joinPoint.getSignature().getName();

Object[] args
= joinPoint.getArgs();

DataSourceKey dataSourceKey
= getAnnotationClassMethod(targetClass, methodName, DataSourceKey.class, args);

if (dataSourceKey != null) {

setDataSourceByKey(joinPoint, dataSourceKey.dataSourceKey());

return;

}

ReadOnlyKey readOnlyKey
= getAnnotationClassMethod(targetClass, methodName, ReadOnlyKey.class, args);

if (readOnlyKey != null) {

setDataSourceByKey(joinPoint, DbType.DB_TYPE_R.getDataSourceKey());

return;

}

dataSourceKey
= (DataSourceKey) targetClass.getAnnotation(DataSourceKey.class);

if (dataSourceKey != null) {

setDataSourceByKey(joinPoint, dataSourceKey.dataSourceKey());

return;

}

setDataSourceByKey(joinPoint, DbType.DB_TYPE_RW.getDataSourceKey());

}

}

复制代码

4、自定义只读注解,@ReadOnlyKey


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ReadOnlyKey {
}

5、配置transaction和AOP

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dynamicDataSource"/>
</bean>

<bean id="dataSourceSwitchingAop" class="com.xxx.common.framework2x.dao.DataSourceSwitchingAop"/>
复制代码
<aop:config>
<aop:aspect id="dataSourceSwitching" ref="dataSourceSwitchingAop" order="0">
<aop:pointcut id="dataSourceSwitchingService"
expression
="execution(* com.xxx.manager..*.*(..))"/>
<aop:before method="switchDataSource" pointcut-ref="dataSourceSwitchingService"/>
</aop:aspect>
</aop:config>
复制代码

以上就完成了基于注解实现动态切换读写数据源。


6、如果想要实现多数据源的切换,则可以自定义注解@DataSourceKey


复制代码

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface DataSourceKey {
/**
* 配置数据源键值
* <p>
* 默认:dataSource.
* </p>
*
*
@return 键值
*/
String dataSourceKey()
default "dataSource"; }
复制代码

在接口方法上增加注解即可。

需要特别注意的地方

1、切换数据源的事务需要放到数据库事务开启前执行。针对上述代码示例中,配置aop时需要指定order(值越小,执行越靠前)

复制代码
<aop:config>
<aop:aspect id="dataSourceSwitching" ref="dataSourceSwitchingAop" order="0">
<aop:pointcut id="dataSourceSwitchingService"
expression
="execution(* com.xxx.manager..*.*(..))"/>
<aop:before method="switchDataSource" pointcut-ref="dataSourceSwitchingService"/>
</aop:aspect>
</aop:config>
复制代码

2、@DataSourceKey可以加在method上,也可以加到class上,优先级是method>class。

3、@ReadOnlyKey只能加到method上。

4、@DatasourceKey和@ReadOnlyKey可以在一个class中混用,优先级是method的@DatasourceKey>method的@ReadOnlyKey>class的@DatasourceKey。

 

posted @ 2018-01-16 21:00  星朝  阅读(304)  评论(0)    收藏  举报