Spring MVC+Mybatis 多数据源配置

随着业务的不断扩张,应用压力逐渐增大,特别是数据库。不论从读写分离还是分库的方法来提高应用的性能,都需要涉及到多数据源问题。本文主要介绍在Spring MVC+Mybatis下的多数据源配置。主要通过Spring提供的AbstractRoutingDataSource来实现多数据源。

1. 继承AbstractRoutingDataSource

AbstractRoutingDataSource 是spring提供的一个多数据源抽象类。spring会在使用事务的地方来调用此类的determineCurrentLookupKey()方法来获取数据源的key值。我们继承此抽象类并实现此方法:

package com.ctitc.collect.manage.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * 
 * @author zongbo
 * 实现spring多路由配置,由spring调用
 */
public class DataSourceRouter extends AbstractRoutingDataSource {
 
 // 获取数据源名称
 protected Object determineCurrentLookupKey() {
  return HandleDataSource.getDataSource();
 }

}

2. 线程内部数据源处理类

DataSourceRouter 类中通过HandleDataSource.getDataSource()获取数据源的key值。此方法应该和线程绑定。

package com.ctitc.collect.manage.datasource;
/**
 * 线程相关的数据源处理类
 * @author zongbo
 *
 */
public class HandleDataSource {
 // 数据源名称线程池
 private static final ThreadLocal holder = new ThreadLocal();

 /**
  * 设置数据源
  * @param datasource 数据源名称
  */
 public static void setDataSource(String datasource) {
  holder.set(datasource);
 }
 /**
  * 获取数据源
  * @return 数据源名称
  */
 public static String getDataSource() {
  return holder.get();
 }
 /**
  * 清空数据源
  */
 public static void clearDataSource() {
  holder.remove();
 }
}

3. 自定义数据源注解类

对于spring来说,注解即简单方便且可读性也高。所以,我们也通过注解在service的方法前指定所用的数据源。我们先定义自己的注解类,其中value为数据源的key值。

package com.ctitc.collect.manage.datasource;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 数据源注解类
 * @author zongbo
 *
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    String value();
}

4. AOP 拦截service并切换数据源

指定注解以后,我们可以通过AOP拦截所有service方法,在方法执行之前获取方法上的注解:即数据源的key值。

package com.ctitc.collect.manage.datasource;

import java.lang.reflect.Method;
import java.text.MessageFormat;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * 切换数据源(不同方法调用不同数据源)
 */
@Aspect
@Component
@Order(1) //请注意:这里order一定要小于tx:annotation-driven的order,即先执行DataSourceAspect切面,再执行事务切面,才能获取到最终的数据源
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {
    static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);

    /**
     * 切入点 service包及子孙包下的所有类
     */
    @Pointcut("execution(* com.ctitc.collect.service..*.*(..))")
    public void aspect() {
    }

    /**
     * 配置前置通知,使用在方法aspect()上注册的切入点
     */
    @Before("aspect()")
    public void before(JoinPoint point) {
        Class target = point.getTarget().getClass();
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod() ;
        DataSource dataSource = null ;
        //从类初始化
        dataSource = this.getDataSource(target, method) ;
        //从接口初始化
        if(dataSource == null){
            for (Class clazz : target.getInterfaces()) {
                dataSource = getDataSource(clazz, method);
                if(dataSource != null){
                    break ;//从某个接口中一旦发现注解,不再循环
                }
            }
        }
        
        if(dataSource != null && !StringUtils.isEmpty(dataSource.value()) ){
            HandleDataSource.setDataSource(dataSource.value());
        }
    }

    @After("aspect()")
    public void after(JoinPoint point) {
        //使用完记得清空
        HandleDataSource.setDataSource(null);
    }
    
    
    /**
     * 获取方法或类的注解对象DataSource
     * @param target    类class
     * @param method    方法
     * @return DataSource
     */
    public DataSource getDataSource(Class target, Method method){
        try {
            //1.优先方法注解
            Class[] types = method.getParameterTypes();
            Method m = target.getMethod(method.getName(), types);
            if (m != null && m.isAnnotationPresent(DataSource.class)) {
                return m.getAnnotation(DataSource.class);
            }
            //2.其次类注解
            if (target.isAnnotationPresent(DataSource.class)) {
                return target.getAnnotation(DataSource.class);
            }
            
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(MessageFormat.format("通过注解切换数据源时发生异常[class={0},method={1}]:"
                    , target.getName(), method.getName()),e)  ;
        }
        return null ;
    }
}

5. 数据源配置

假设我有两个库:业务库和订单库。先要配置这两个数据源

  • 业务数据源

  业务数据源
     
        
        
        
                
        
        
        
        
        
        
             
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
             
        
        
          
   
  • 订单数据源
   
     订单数据源
        
        
        
                
        
   
  • dataSource 则是刚刚实现的DataSourceRouter,且需要指定此类的 targetDataSources属性和 defaultTargetDataSource属性。

targetDataSources :数据源列表,key-value形式,即上面配置的两个数据源
defaultTargetDataSource:默认数据源,如果未指定数据源 或者指定的数据源不存在的话 默认使用这个数据源


  多数据源路由
  
   
    
    
    
   
  
  
  
  
 

6. AOP的顺序问题

由于我使用的注解式事务,和我们的AOP数据源切面有一个顺序的关系。数据源切换必须先执行,数据库事务才能获取到正确的数据源。所以要明确指定 注解式事务和 我们AOP数据源切面的先后顺序。

  • 我们数据源切换的AOP是通过注解来实现的,只需要在AOP类上加上一个order(1)注解即可,其中1代表顺序号。
  • 注解式事务的是通过xml配置启动

7. 示例Demo

在每个service方法前使用@DataSource("数据源key")注解即可。

@Override
 @DataSource("busi")
 @Transactional(readOnly = true, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
 public List test1() {
  // TODO Auto-generated method stub
  return coreMapper.getVersion();
 }
 
 @Override
 @DataSource("order")
 @Transactional(readOnly = true, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
 public List test2() {
  // TODO Auto-generated method stub
  return coreMapper.getVersion();
 }

你可能感兴趣的:(Spring MVC+Mybatis 多数据源配置)