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>();</span><span style="color: #008000;">/**</span><span style="color: #008000;"></br> * 数据库源类型</br> * <p></br> * 配置数据源的时候,请遵守以下约束:<br></br> * 读写:dataSourceKeyRW;<br></br> * 读:dataSourceKeyR.</br> * </p></br> </span><span style="color: #008000;">*/</span></br> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">enum</span><span style="color: #000000;"> DbType {</br> DB_TYPE_RW(</span>"dataSourceKeyRW"), DB_TYPE_R("dataSourceKeyR"<span style="color: #000000;">);</br> </span><span style="color: #0000ff;">private</span><span style="color: #000000;"> String dataSourceKey;</br> DbType(String dataSourceKey) {</br> </span><span style="color: #0000ff;">this</span>.dataSourceKey =<span style="color: #000000;"> dataSourceKey;</br> }</br> </span><span style="color: #0000ff;">public</span><span style="color: #000000;"> String getDataSourceKey() {</br> </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> dataSourceKey;</br> }</br> } </span><span style="color: #008000;">/**</span><span style="color: #008000;"></br> * 获取数据源</br> * <p></br> * 如果未设置,默认返回读数据源</br> * </p></br> *</br> * </span><span style="color: #808080;">@return</span><span style="color: #008000;"> 数据源键值</br> </span><span style="color: #008000;">*/</span></br> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> String getDataSource() {</br> String dataSource </span>=<span style="color: #000000;"> contextHolder.get();</br> </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (StringUtils.isEmpty(dataSource)) {</br> dataSource </span>=<span style="color: #000000;"> DbType.DB_TYPE_RW.dataSourceKey;</br> }</br> </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> dataSource;</br> }</br> </span><span style="color: #008000;">/**</span><span style="color: #008000;"> * 设置数据源</br> * * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> dataSourceKey 数据源键值</br> </span><span style="color: #008000;">*/</span></br> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> setDataSource(String dataSourceKey) {</br> contextHolder.set(dataSourceKey);</br> }</br>
}
注:定义了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()); }</span><span style="color: #008000;">/**</span><span style="color: #008000;"></br> * 切换数据源</br> * <p></br> * 切换优先级由高到底如下;方法上注解DataSourceKey,方法上注解ReadOnlyKey,类上注解DataSourceKey;<br></br> * 如果未注解,则默认设置写数据源.</br> * </p></br> * * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> joinPoint 切点</br> * </span><span style="color: #808080;">@see</span><span style="color: #008000;"> DataSourceKey</br> * </span><span style="color: #808080;">@see</span><span style="color: #008000;"> ReadOnlyKey</br> * </span><span style="color: #808080;">@see</span><span style="color: #008000;"> DbType</br> </span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> switchDataSource(JoinPoint joinPoint) {</br> Class</span><?> targetClass =<span style="color: #000000;"> joinPoint.getTarget().getClass();</br> String methodName </span>=<span style="color: #000000;"> joinPoint.getSignature().getName();</br> Object[] args </span>=<span style="color: #000000;"> joinPoint.getArgs();</br> DataSourceKey dataSourceKey </span>= getAnnotationClassMethod(targetClass, methodName, DataSourceKey.<span style="color: #0000ff;">class</span><span style="color: #000000;">, args);</br> </span><span style="color: #0000ff;">if</span> (dataSourceKey != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {</br> setDataSourceByKey(joinPoint, dataSourceKey.dataSourceKey());</br> </span><span style="color: #0000ff;">return</span><span style="color: #000000;">;</br> }</br> ReadOnlyKey readOnlyKey </span>= getAnnotationClassMethod(targetClass, methodName, ReadOnlyKey.<span style="color: #0000ff;">class</span><span style="color: #000000;">, args);</br> </span><span style="color: #0000ff;">if</span> (readOnlyKey != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {</br> setDataSourceByKey(joinPoint, DbType.DB_TYPE_R.getDataSourceKey());</br> </span><span style="color: #0000ff;">return</span><span style="color: #000000;">;</br> }</br> dataSourceKey </span>= (DataSourceKey) targetClass.getAnnotation(DataSourceKey.<span style="color: #0000ff;">class</span><span style="color: #000000;">);</br> </span><span style="color: #0000ff;">if</span> (dataSourceKey != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {</br> setDataSourceByKey(joinPoint, dataSourceKey.dataSourceKey());</br> </span><span style="color: #0000ff;">return</span><span style="color: #000000;">;</br> }</br> setDataSourceByKey(joinPoint, DbType.DB_TYPE_RW.getDataSourceKey());</br> }</br>
}
4、自定义只读注解,@ReadOnlyKey
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ReadOnlyKey { }
5、配置transaction和AOP
<bean > <property name="dataSource" ref="dynamicDataSource"/> </bean>
<bean />
<aop:config> <aop:aspect > <aop:pointcut > 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 > <aop:pointcut > 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。
代码实现读写数据库分离
武器
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>();</span><span style="color: #008000;">/**</span><span style="color: #008000;"></br> * 数据库源类型</br> * <p></br> * 配置数据源的时候,请遵守以下约束:<br></br> * 读写:dataSourceKeyRW;<br></br> * 读:dataSourceKeyR.</br> * </p></br> </span><span style="color: #008000;">*/</span></br> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">enum</span><span style="color: #000000;"> DbType {</br> DB_TYPE_RW(</span>"dataSourceKeyRW"), DB_TYPE_R("dataSourceKeyR"<span style="color: #000000;">);</br> </span><span style="color: #0000ff;">private</span><span style="color: #000000;"> String dataSourceKey;</br> DbType(String dataSourceKey) {</br> </span><span style="color: #0000ff;">this</span>.dataSourceKey =<span style="color: #000000;"> dataSourceKey;</br> }</br> </span><span style="color: #0000ff;">public</span><span style="color: #000000;"> String getDataSourceKey() {</br> </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> dataSourceKey;</br> }</br> } </span><span style="color: #008000;">/**</span><span style="color: #008000;"></br> * 获取数据源</br> * <p></br> * 如果未设置,默认返回读数据源</br> * </p></br> *</br> * </span><span style="color: #808080;">@return</span><span style="color: #008000;"> 数据源键值</br> </span><span style="color: #008000;">*/</span></br> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> String getDataSource() {</br> String dataSource </span>=<span style="color: #000000;"> contextHolder.get();</br> </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (StringUtils.isEmpty(dataSource)) {</br> dataSource </span>=<span style="color: #000000;"> DbType.DB_TYPE_RW.dataSourceKey;</br> }</br> </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> dataSource;</br> }</br> </span><span style="color: #008000;">/**</span><span style="color: #008000;"> * 设置数据源</br> * * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> dataSourceKey 数据源键值</br> </span><span style="color: #008000;">*/</span></br> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> setDataSource(String dataSourceKey) {</br> contextHolder.set(dataSourceKey);</br> }</br>
}
注:定义了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()); }</span><span style="color: #008000;">/**</span><span style="color: #008000;"></br> * 切换数据源</br> * <p></br> * 切换优先级由高到底如下;方法上注解DataSourceKey,方法上注解ReadOnlyKey,类上注解DataSourceKey;<br></br> * 如果未注解,则默认设置写数据源.</br> * </p></br> * * </span><span style="color: #808080;">@param</span><span style="color: #008000;"> joinPoint 切点</br> * </span><span style="color: #808080;">@see</span><span style="color: #008000;"> DataSourceKey</br> * </span><span style="color: #808080;">@see</span><span style="color: #008000;"> ReadOnlyKey</br> * </span><span style="color: #808080;">@see</span><span style="color: #008000;"> DbType</br> </span><span style="color: #008000;">*/</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> switchDataSource(JoinPoint joinPoint) {</br> Class</span><?> targetClass =<span style="color: #000000;"> joinPoint.getTarget().getClass();</br> String methodName </span>=<span style="color: #000000;"> joinPoint.getSignature().getName();</br> Object[] args </span>=<span style="color: #000000;"> joinPoint.getArgs();</br> DataSourceKey dataSourceKey </span>= getAnnotationClassMethod(targetClass, methodName, DataSourceKey.<span style="color: #0000ff;">class</span><span style="color: #000000;">, args);</br> </span><span style="color: #0000ff;">if</span> (dataSourceKey != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {</br> setDataSourceByKey(joinPoint, dataSourceKey.dataSourceKey());</br> </span><span style="color: #0000ff;">return</span><span style="color: #000000;">;</br> }</br> ReadOnlyKey readOnlyKey </span>= getAnnotationClassMethod(targetClass, methodName, ReadOnlyKey.<span style="color: #0000ff;">class</span><span style="color: #000000;">, args);</br> </span><span style="color: #0000ff;">if</span> (readOnlyKey != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {</br> setDataSourceByKey(joinPoint, DbType.DB_TYPE_R.getDataSourceKey());</br> </span><span style="color: #0000ff;">return</span><span style="color: #000000;">;</br> }</br> dataSourceKey </span>= (DataSourceKey) targetClass.getAnnotation(DataSourceKey.<span style="color: #0000ff;">class</span><span style="color: #000000;">);</br> </span><span style="color: #0000ff;">if</span> (dataSourceKey != <span style="color: #0000ff;">null</span><span style="color: #000000;">) {</br> setDataSourceByKey(joinPoint, dataSourceKey.dataSourceKey());</br> </span><span style="color: #0000ff;">return</span><span style="color: #000000;">;</br> }</br> setDataSourceByKey(joinPoint, DbType.DB_TYPE_RW.getDataSourceKey());</br> }</br>
}
4、自定义只读注解,@ReadOnlyKey
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ReadOnlyKey { }
5、配置transaction和AOP
<bean > <property name="dataSource" ref="dynamicDataSource"/> </bean>
<bean />
<aop:config> <aop:aspect > <aop:pointcut > 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 > <aop:pointcut > 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。