springAOP实现基于注解的数据源动态切换

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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。

 

以上是关于springAOP实现基于注解的数据源动态切换的主要内容,如果未能解决你的问题,请参考以下文章

Spring Aop实现方式(注解和Xml)

Spring Aop实现方式(注解和Xml)

SpringAOP+注解实现简单的日志管理

SpringAOP+注解实现简单的日志管理

基于注解的Spring AOP的配置和使用

基础篇——代理模式之SpringAOP