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

  • 相关阅读:
    [课程设计]任务进度条&开发日志目录
    《软件工程》课程设计-团队成立
    软件工程学期总结
    第三次冲刺及课程设计 617
    学术诚信与职业道德
    第二个冲刺
    操作系统 实验四主存空间的分配和回收
    0526 《构建之法》第8-10章读后感
    SCRUM项目 6.0
    SCRUM项目 5.0
  • 原文地址:https://www.cnblogs.com/hanstrovsky/p/14554722.html
Copyright © 2011-2022 走看看