zoukankan      html  css  js  c++  java
  • 传统Spring项目如何配置多数据源

    思路:

    Spring 预留了 AbstractRoutingDataSource 允许开发者进行数据源的切换,只要通过determineCurrentLookupKey() 方法,提供对应数据源的 lookupKey,SPing 便会选择对应的数据源。

    实现:

    第一步:创建一个DynamicDataSource的类,继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,代码如下:

    public class DynamicDataSource extends AbstractRoutingDataSource {
        /**
         * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
         */
        private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<>();
    
        public static void setDataSource(String dataSource) {
            THREAD_DATA_SOURCE.set(dataSource);
        }
    
        public static void clearDataSource() {
            THREAD_DATA_SOURCE.remove();
        }
    
        @Override
        protected Object determineCurrentLookupKey() {
            // 从自定义的位置获取数据源标识
            return THREAD_DATA_SOURCE.get();
        }
    }
    

    第二步:配置多个数据源和第一步里创建的DynamicDataSource的bean,简化的配置如下:

     <!--创建数据源1,连接数据库db1 -->
    <bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${db1.driver}" />
        <property name="url" value="${db1.url}" />
        <property name="username" value="${db1.username}" />
        <property name="password" value="${db1.password}" />
    </bean>
    <!--创建数据源2,连接数据库db2 -->
    <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${db2.driver}" />
        <property name="url" value="${db2.url}" />
        <property name="username" value="${db2.username}" />
        <property name="password" value="${db2.password}" />
    </bean>
    
    <bean id="dynamicDataSource" class="com.test.context.datasource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <!-- 指定lookupKey和与之对应的数据源 -->
                <entry key="dataSource1" value-ref="dataSource1"></entry>
                <entry key="dataSource2" value-ref="dataSource2"></entry>
            </map>
        </property>
        <!-- 这里可以指定默认的数据源 -->
        <property name="defaultTargetDataSource" ref="dataSource1" />
    </bean>   
    

    到这里已经可以使用多数据源了,在操作数据库之前只要DynamicDataSource.setDataSource("dataSource2")即可切换到数据源2并对数据库db2进行操作了。

    示例代码如下:

    @Service
    public class DataServiceImpl implements DataService {
        @Autowired
        private DataMapper dataMapper;
    
        @Override
        public List<Map<String, Object>> getList1() {
            // 没有指定,则默认使用数据源1
            return dataMapper.getList1();
        }
    
        @Override
        public List<Map<String, Object>> getList2() {
            // 指定切换到数据源2
            DynamicDataSource.setDataSource("dataSource2");
            return dataMapper.getList2();
        }
    }
    

    -------------------------------------------------------------华丽的分割线--------------------------------------------------------------------

    但是手动切换十分繁琐,容易遗漏,下面看自动的。
    首先,我们得定义一个名为DataSource的注解,代码如下:

    @Target({ TYPE, METHOD })
    @Retention(RUNTIME)
    public @interface DataSource {
        String value();
    }
    

    然后,定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSource的线程变量中:

    public class DataSourceAspect implements Ordered{
    
        /**
         * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
         *
         * @param point
         * @throws Exception
         */
        public void intercept(JoinPoint point) throws Exception {
            Class<?> target = point.getTarget().getClass();
            MethodSignature signature = (MethodSignature) point.getSignature();
            // 默认使用目标类型的注解,如果没有则使用其实现接口的注解
            for (Class<?> clazz : target.getInterfaces()) {
                resolveDataSource(clazz, signature.getMethod());
            }
            resolveDataSource(target, signature.getMethod());
        }
    
        /**
         * 提取目标对象方法注解和类型注解中的数据源标识
         *
         * @param clazz
         * @param method
         */
        private void resolveDataSource(Class<?> clazz, Method method) {
            try {
                Class<?>[] types = method.getParameterTypes();
                // 默认使用类型注解
                if (clazz.isAnnotationPresent(DataSource.class)) {
                    DataSource source = clazz.getAnnotation(DataSource.class);
                    DynamicDataSource.setDataSource(source.value());
                } else {
                    DynamicDataSource.clearDataSource();
                }
                // 方法注解可以覆盖类型注解
                Method m = clazz.getMethod(method.getName(), types);
                if (m != null && m.isAnnotationPresent(DataSource.class)) {
                    DataSource source = m.getAnnotation(DataSource.class);
                    DynamicDataSource.setDataSource(source.value());
                }
            } catch (Exception e) {
                System.out.println(clazz + ":" + e.getMessage());
            }
        }
        
        @Override
        public int getOrder() {
            /* 由于接口实现配置了事务管理,事务的优先级会高于数据源切换。一旦事务开启,再切换数据源是无效的,
            所以这里需要修改它们的次序。同时事务的配置中增加order为2,这样,Spring会优先执行通知类方法,之后再开启事务。
            <tx:annotation-driven transaction-manager="transactionManager" order="2" /> */
            return 1;
        }
    
    }
    

    最后在spring配置文件中配置拦截规则就可以了,比如拦截service层或者dao层的所有方法:

    <bean id="dataSourceAspect" class="com.test.context.datasource.DataSourceAspect" />
        <aop:config>
            <aop:aspect ref="dataSourceAspect">
                <!-- 拦截所有service方法 -->
                <aop:pointcut id="dataSourcePointcut" expression="execution(* com.test.*.dao.*.*(..))"/>
                <aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
            </aop:aspect>
        </aop:config>
    </bean>
    

    OK,这样就可以直接在类或者方法上使用注解@DataSource来指定数据源,不需要每次都手动设置了。

    示例代码如下:

    @Service
    // 默认DataServiceImpl下的所有方法均访问数据源1
    @DataSource("dataSource1")
    public class DataServiceImpl implements DataService {
        @Autowired
        private DataMapper dataMapper;
    
        @Override
        public List<Map<String, Object>> getList1() {
            // 不指定,则默认使用数据源1
            return dataMapper.getList1();
        }
    
        @Override
        // 覆盖类上指定的,使用数据源2
        @DataSource("dataSource2")
        public List<Map<String, Object>> getList2() {
            return dataMapper.getList2();
        }
    }
    

    提示:注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。也就是说如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。

    原文:
    https://www.iteye.com/blog/somefuture-2306320
    https://www.cnblogs.com/jpfss/p/8118263.html

  • 相关阅读:
    085 Maximal Rectangle 最大矩形
    084 Largest Rectangle in Histogram 柱状图中最大的矩形
    083 Remove Duplicates from Sorted List 有序链表中删除重复的结点
    082 Remove Duplicates from Sorted List II 有序的链表删除重复的结点 II
    081 Search in Rotated Sorted Array II 搜索旋转排序数组 ||
    080 Remove Duplicates from Sorted Array II 从排序阵列中删除重复 II
    079 Word Search 单词搜索
    078 Subsets 子集
    bzoj2326: [HNOI2011]数学作业
    bzoj2152: 聪聪可可
  • 原文地址:https://www.cnblogs.com/hanstrovsky/p/14554722.html
Copyright © 2011-2022 走看看