• 多数据源动态配置及事务控制


    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();
      
      .............
    
    }

  • 相关阅读:
    ORM
    数据库事务课上代码
    数据存储——SQLite数据库存储——API
    事务的ACID特性
    数据库练习3
    数据存储——SQLite数据库存储——SQL语句——DML数据操作语言、内置函数聚合函数
    数据库练习2
    数据存储——SQLite数据库存储——SQL语句——DQL数据查询语言
    数据库练习
    《那些事之Log4j》什么是log4j?【专题一】
  • 原文地址:https://www.cnblogs.com/qingchen521/p/9160239.html
Copyright © 2020-2023  润新知