zoukankan      html  css  js  c++  java
  • 多数据源动态配置及事务控制

    1、动态数据源切换时,如何保证事务

      目前事务最灵活的方式,是使用spring的声明式事务,本质是利用了spring的aop,在执行数据库操作前后,加上事务处理。

      spring的事务管理,是基于数据源的,所以如果要实现动态数据源切换,而且在同一个数据源中保证事务是起作用的话,就需要注意二者的顺序问题,即:在事务起作用之前就要把数据源切换回来。

      举一个例子:web开发常见是三层结构:controller、service、dao。一般事务都会在service层添加,如果使用spring的声明式事务管理,在调用service层代码之前,spring会通过aop的方式动态添加事务控制代码,所以如果要想保证事务是有效的,那么就必须在spring添加事务之前把数据源动态切换过来,也就是动态切换数据源的aop要至少在service上添加,而且要在spring声明式事务aop之前添加.根据上面分析:

    • 最简单的方式是把动态切换数据源的aop加到controller层,这样在controller层里面就可以确定下来数据源了。不过,这样有一个缺点就是,每一个controller绑定了一个数据源,不灵活。对于这种:一个请求,需要使用两个以上数据源中的数据完成的业务时,就无法实现了。
    • 针对上面的这种问题,可以考虑把动态切换数据源的aop放到service层,但要注意一定要在事务aop之前来完成。这样,对于一个需要多个数据源数据的请求,我们只需要在controller里面注入多个service实现即可。但这种做法的问题在于,controller层里面会涉及到一些不必要的业务代码,例如:合并两个数据源中的list…
    • 此外,针对上面的问题,还可以再考虑一种方案,就是把事务控制到dao层,然后在service层里面动态切换数据源。

    2、实例

    本例子中,对不同数据源分包(package)管理,同一包下的代码使用了同一数据源。
    1、写一个DynamicDataSource类继承AbstractRoutingDataSource,并实现determineCurrentLookupKey方法

    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return DatabaseContextHolder.getCustomerType(); 
      }
    }

    2、利用ThreadLocal解决线程安全问题:

    public class DatabaseContextHolder {
        public static final String DATA_SOURCE_A = "dataSource";
        public static final String DATA_SOURCE_B = "dataSource2";
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
        public static void setCustomerType(String customerType) {
            contextHolder.set(customerType);
        }
        public static String getCustomerType() {
            return contextHolder.get();
        }
        public static void clearCustomerType() {
            contextHolder.remove();
        }
    }

    3、定义一个数据源切面类,通过aop来控制数据源的切换:

    import org.aspectj.lang.JoinPoint;
    
    public class DataSourceInterceptor {
    
        public void setdataSourceMysql(JoinPoint jp) {
            DatabaseContextHolder.setCustomerType("dataSourceMySql");
        }
        
        public void setdataSourceOracle(JoinPoint jp) {
            DatabaseContextHolder.setCustomerType("dataSourceOracle");
        }
    }

    4、在spring的application.xml中配置多个dataSource:

    <!-- 数据源1 -->
    <bean id="dataSourceMysql" class="org.apache.commons.dbcp.BasicDataSource">
               <property name="driverClassName" value="net.sourceforge.jtds.jdbc.Driver"></property>
            <property name="url" value="jdbc:jtds:sqlserver://1.1.1.1:3306;databaseName=standards"></property>
            <property name="username" value="admin"></property>
            <property name="password" value="admin"></property>
    </bean>
    <!-- 数据源2 -->
    <bean id="dataSourceOracle" class="org.apache.commons.dbcp.BasicDataSource">
               <property name="driverClassName" value="net.sourceforge.jtds.jdbc.Driver"></property>
            <property name="url" value="jdbc:jtds:sqlserver://2.2.2.2:1433;databaseName=standards"></property>
            <property name="username" value="admin"></property>
            <property name="password" value="admin"></property>
    </bean>
    
    <bean id="dataSource" class="com.core.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="dataSourceMySql" value-ref="dataSourceMySql" />
                <entry key="dataSourceOracle" value-ref="dataSourceOracle" />
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="dataSourceMySql" /> <!-- 默认使用的数据源 -->
    </bean>
    
    <!-- 动态数据源切换aop 先与事务的aop  -->
    <bean id="dataSourceInterceptor" class="com.core.DataSourceInterceptor" />
    <aop:config>
        <aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
            <aop:pointcut id="dsMysql" expression="execution(* com.service.mysql..*.*(..))" />
            <aop:pointcut id="dsOracle" expression="execution(* com.service.oracle..*.*(..))" />
            <aop:before method="setdataSourceMysql" pointcut-ref="dsMysql"/>
            <aop:before method="setdataSourceOracle" pointcut-ref="dsOracle"/>
        </aop:aspect>
    </aop:config>
    
    <!-- 事务管理器, Jdbc单数据源事务 -->
    <bean id="transactionManager"   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 使用annotation定义事务 proxy-target-class="true"-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    3、DynamicDataSource讲解

      再来说下DynamicDataSource的实现原理,DynamicDataSource实现AbstractRoutingDataSource抽象类,然后实现了determineCurrentLookupKey方法,这个方法用于选择具体使用targetDataSources中的哪一个数据源
    <bean id="dataSource" class="com.core.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="dataSourceMySql" value-ref="dataSourceMySql" />
                <entry key="dataSourceOracle" value-ref="dataSourceOracle" />
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="dataSourceMySql" /> <!-- 默认使用的数据源 -->
    </bean>

      可以看到Spring配置中DynamicDataSource设置了两个属性defaultTargetDataSource和targetDataSources,这两个属性定义在AbstractRoutingDataSource,当MyBatis执行查询时会先选择数据源,选择顺序时现根据determineCurrentLookupKey方法返回的值到targetDataSources中去找,若能找到怎返回对应的数据源,若找不到返回默认的数据源defaultTargetDataSource,具体参考AbstractRoutingDataSource的源码

    public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    
        private Map<Object, Object> targetDataSources;
    
        private Object defaultTargetDataSource;
    
        
        /**
         * Retrieve the current target DataSource. Determines the
         * {@link #determineCurrentLookupKey() current lookup key}, performs
         * a lookup in the {@link #setTargetDataSources targetDataSources} map,
         * falls back to the specified
         * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
         * @see #determineCurrentLookupKey()
         */protected DataSource determineTargetDataSource() {
            Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
            Object lookupKey = determineCurrentLookupKey();
            DataSource dataSource = this.resolvedDataSources.get(lookupKey);
            if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
                dataSource = this.resolvedDefaultDataSource;
            }
            if (dataSource == null) {
                throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
            }
            return dataSource;
        }
    
        /**
         * Determine the current lookup key. This will typically be
         * implemented to check a thread-bound transaction context.
         * <p>Allows for arbitrary keys. The returned key needs
         * to match the stored lookup key type, as resolved by the
         * {@link #resolveSpecifiedLookupKey} method.
         */protected abstract Object determineCurrentLookupKey();
      
      .............
    
    }

  • 相关阅读:
    Java实现 LeetCode 455 分发饼干
    Java实现 LeetCode 455 分发饼干
    Java实现 LeetCode 455 分发饼干
    Java实现 LeetCode 454 四数相加 II
    Java实现 LeetCode 454 四数相加 II
    Java实现 LeetCode 454 四数相加 II
    FFmpeg解码H264及swscale缩放详解
    linux中cat more less head tail 命令区别
    C语言字符串操作总结大全(超详细)
    如何使用eclipse进行嵌入式Linux的开发
  • 原文地址:https://www.cnblogs.com/qingchen521/p/9160239.html
Copyright © 2011-2022 走看看