• Spring AOP动态切换数据源


      现在稍微复杂一点的项目,一个数据库也可能搞不定,可能还涉及分布式事务什么的,不过由于现在我只是做一个接口集成的项目,所以分布式就先不用了,用Spring AOP来达到切换数据源,查询不同的数据库就可以了。

      如果以前的我,可能就1个数据库->1个数据源->1个SessionFactory->1个事务管理,按照这样的逻辑,操作一个数据库是没什么问题的,但是两个甚至多个这样的相同配置,这不是要逼死强迫症患者的节奏吗?

      Spring动态切换数据库的原理是通过继承AbstractRoutingDataSource重写determineCurrentLookupKey()方法,来决定使用那个数据库。在开启事务之前,通过改变lookupKey来达到切换数据源目的。

      先写DataSourceHolder用来保存当前线程的数据库源。

    public class DataSourceHolder {
        
        private static final ThreadLocal<String> datasourcce = new ThreadLocal<String>();
        
        public static void setCustomeType(String type){
            datasourcce.set(type);
        }
        
        public static String getCustomeType(){
            return datasourcce.get();
        }
        
        public static void remove(){
            datasourcce.remove();
        }
    }
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    public class DynamicDataSource extends AbstractRoutingDataSource{
    
        @Override
        protected Object determineCurrentLookupKey() {
            return DataSourceHolder.getCustomeType();
        }
    
    }

      ThreadLocal用作保存数据库源的key就可以了,相应的数据库源会在切换的时候从AbstractRoutingDataSource的Map<Object, Object> targetDataSources中获取。

    <bean name="db1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
         <property name="url" value="${jdbc.db1.url}" />
         <property name="username" value="${jdbc.db1.username}" />
         <property name="password" value="${jdbc.db1.password}" />        
     </bean>
        
     <bean name="db2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
            <property name="url" value="${jdbc.db2.url}" />
            <property name="username" value="${jdbc.db2.username}" />
            <property name="password" value="${jdbc.db2.password}" />              
        </bean>
        
        <bean id="dataSource" class="com.test.dynamic.datasource.DynamicDataSource">
            <property name="targetDataSources">
                <map key-type="java.lang.String">
                    <entry key="db1" value-ref="db1" />
                    <entry key="db2" value-ref="db2" />
                </map>
            </property>
            <property name="defaultTargetDataSource" ref="db1" />
        </bean>
        
        <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <property name="packagesToScan">
                <list>
                    <value>${packagesToScan}</value>
                </list>
            </property>
            <property name="hibernateProperties">
                <props>
                    <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
                    <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                </props>
            </property>
        </bean>
     
        <bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
            <property name="sessionFactory" ref="sessionFactory"/>
        </bean>
        
        <aop:config>
            <aop:pointcut expression="${aop.expression}" id="bussinessService"/>
            <aop:advisor advice-ref="txAdvice" pointcut-ref="bussinessService" order="2"/>

          <aop:aspect ref="dataSourceAspect" order="1">
            <aop:before method="changeDateSource" pointcut="@annotation(com.test.annotation.DataSource)"/>
          </aop:aspect>

        </aop:config>
        
        <tx:advice id="txAdvice" transaction-manager="txManager">
            <tx:attributes>
                <tx:method name="*" propagation="REQUIRED" />  
            </tx:attributes>
        </tx:advice>

      这次使用的是@annotation的方式的AOP切面,当然也可以使用基于正则的AOP切面,接下来写DataSourceAspect。

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface DataSource {
        public String name() default "";
    }
    import java.lang.reflect.Method;
    
    import org.aspectj.lang.JoinPoint;
    import org.springframework.stereotype.Component;
    
    import com.test.annotation.DataSource;
    
    @Component
    public class DataSourceAspect {
    
        public void changeDateSource(JoinPoint jp){
            try{
                String methodName = jp.getSignature().getName();
                Class<?> targetClass = Class.forName(jp.getTarget().getClass().getName());
                for(Method method : targetClass.getMethods()){
                    if(methodName.equals(method.getName())){
                        Class<?>[] args = method.getParameterTypes();
                        if(args.length == jp.getArgs().length){
                            DataSource ds = method.getAnnotation(DataSource.class);
                            DataSourceHolder.setCustomeType(ds.name());
                        }
                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        
    }

      这个使用的时候很简单,只要在需要切换数据源上的方法加一个注解@DataSource(name="db1"),就可以了。由于我们做事务控制的在Service层,所以在Dao层上切换是不行的。只能在Controller层和Service做切换,而且在Service切换需要在切面上加order属性,order属性越小,就越先执行,只要切换的逻辑在开始事务前执行就可以了。

      1、那么问题来了,可以在Service同一个方法上访问两个不同的数据库吗?

      不可以的。但是可以在Controller访问Service的两个不同方法。

      2、不同的数据库方言要换吗?

      其实是不用换的,方言不配置也可以(其实还没试过,理论上-.-),经试验,方言默认为默认数据源的方言,由mysql切换为oracle需要注意。

      3、要注意什么?

      注意hibernate扫面默认的数据源就好了,hibernate.hbm2ddl.auto设置为validate,数据库表手动建。  

      

  • 相关阅读:
    PCA本质和SVD
    特征工程(转载)
    python入门基础代码
    长尾理论
    金融行业数据分析
    [rancher-net]
    rancher中使用ingress-lbs做负载均衡
    python 高级语言特性
    docker从初识到深入
    关于容器技术的发展以及虚拟化技术的总结
  • 原文地址:https://www.cnblogs.com/ginponson/p/5264107.html
Copyright © 2020-2023  润新知