zoukankan      html  css  js  c++  java
  • 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。

  • 相关阅读:
    20145223《Java程序程序设计》课程总结
    20145223《Java程序程序设计》第10周学习总结
    20145223《Java程序程序设计》实验报告5
    20145223《Java程序程序设计》第9周学习总结
    20145223 《Java程序程序设计》实验报告4
    20145223《Java程序程序设计》第8周学习总结
    20145223《Java程序设计》实验报告3
    20145223《Java程序程序设计》第7周学习总结
    20145223《Java程序程序设计》实验报告二
    node_promise
  • 原文地址:https://www.cnblogs.com/kaitokidzhao/p/5629413.html
Copyright © 2011-2022 走看看