Mybatis+Spring 多数据源方案

方案一

通过Spring AOP在业务层实现读写分离,在DAO层定义切面,利用Spring的AbstractRoutingDataSource解决多数据源的问题,实现动态选择数据源

dao层代码如下:

public interface InvTsReturnStockDao {
    @Master
    Integer insert(InvTsReturnStock invTsReturnStock);
    @Slave
    List selectAll(@Param("status") String status);
}

执行流程:
1、调用insert();的方法时候,切面(Spring Aspect)先根据注解设置数据源类型。
2、然后需要获取数据库连接this.determineTargetDataSource().getConnection();this.determineTargetDataSource()获得选定的数据源。

实现过程:

  • 1、定义多数据源

        
        
        
    
    
        
        
        
    

    
        
            
                
                
            
        
    
  • 2、自定义路由数据源
public class ReadWriteRoutingDataSourceHolder {
    private static final ThreadLocal  holder       = new ThreadLocal();
    private static final ThreadLocal masterHolder = new ThreadLocal();

    public static void put(String value) {
        holder.set(value);
    }

    public static String get() {
        if (null != masterHolder.get() && masterHolder.get()) {
            return "";
        }
        return holder.get();
    }

    public static void clear() {
        holder.remove();
    }

    /**
     * 设置是否需要整个事务都需要主库
     * @param isAlwaysMaster
     */
    public static void setIsAlwaysMaster(Boolean isAlwaysMaster) {
        masterHolder.set(isAlwaysMaster);
    }

    /**
     * 清除整个事务需要主库设置
     */
    public static void clearIsAlwaysMaster() {
        masterHolder.remove();
    }
} 

通过ThreadLocal 来记录当前线程选择的数据源,通过切面去设置ThreadLocal当前线程选择的数据源.ThreadLocal是实现关键,使在整个线程调用过程中设置的数据源与别的线程隔离

public class ReadWriteRoutingDataSource extends AbstractRoutingDataSource {
    private static final Logger LOGGER = LoggerFactory.getLogger(ReadWriteRoutingDataSource.class);

    @Override
    protected Object determineCurrentLookupKey() {
        String routingDataSourceType = ReadWriteRoutingDataSourceHolder.get();
        if (StringUtil.isEmpty(routingDataSourceType)) {
            routingDataSourceType = RoutingDataSourceType.MASTER.toString();
        }
        ReadWriteRoutingDataSourceHolder.clear();

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("############## ReadWriteRoutingDataSource:" + routingDataSourceType
                         + " ##############");
        }
        return routingDataSourceType;
    }

}

determineCurrentLookupKey()方法,返回选择的数据源key,返回的数据源key是通过切面设置的

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    private Map targetDataSources;
    private Object defaultTargetDataSource;
    private boolean lenientFallback = true;
    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
    private Map resolvedDataSources;
    private DataSource resolvedDefaultDataSource;

    public AbstractRoutingDataSource() {
    }

    public void setTargetDataSources(Map targetDataSources) {
        this.targetDataSources = targetDataSources;
    }

    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        this.defaultTargetDataSource = defaultTargetDataSource;
    }

    public void setLenientFallback(boolean lenientFallback) {
        this.lenientFallback = lenientFallback;
    }

    public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
        this.dataSourceLookup = (DataSourceLookup)(dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
    }
    //该bean 初始化的时候会调用
    public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        } else {
            this.resolvedDataSources = new HashMap(this.targetDataSources.size());
            //遍历步骤1 第二段代码设置进去的数据源
            Iterator var2 = this.targetDataSources.entrySet().iterator();

            while(var2.hasNext()) {
                Entry entry = (Entry)var2.next();
                Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());
                DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());
                //将数据源以key-value 的形式放到resolvedDataSources中
                this.resolvedDataSources.put(lookupKey, dataSource);
            }

            if (this.defaultTargetDataSource != null) {
                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
            }

        }
    }

    protected Object resolveSpecifiedLookupKey(Object lookupKey) {
        return lookupKey;
    }

    protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
        if (dataSource instanceof DataSource) {
            return (DataSource)dataSource;
        } else if (dataSource instanceof String) {
            return this.dataSourceLookup.getDataSource((String)dataSource);
        } else {
            throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
        }
    }
    //获取数据库连接
    public Connection getConnection() throws SQLException {
        return this.determineTargetDataSource().getConnection();
    }

    public Connection getConnection(String username, String password) throws SQLException {
        return this.determineTargetDataSource().getConnection(username, password);
    }

    public  T unwrap(Class iface) throws SQLException {
        return iface.isInstance(this) ? this : this.determineTargetDataSource().unwrap(iface);
    }

    public boolean isWrapperFor(Class iface) throws SQLException {
        return iface.isInstance(this) || this.determineTargetDataSource().isWrapperFor(iface);
    }
    //获取数据源
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
      //调用第一段代码获得选中的
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource dataSource = 
//获得数据源的key
(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 + "]");
        } else {
            return dataSource;
        }
    }

    protected abstract Object determineCurrentLookupKey();
}

  • 3、定义注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Master {
}
  • 4、定义切面
@Aspect
public class ReadWriteRoutingDataSourceAspect {

    private static final Logger                           LOGGER               = LoggerFactory
                                                                                   .getLogger(ReadWriteRoutingDataSourceAspect.class);

    /**
     * Map>
     */
    private static final Map> classMethodSourceMap = new ConcurrentHashMap>();
    //该切面切所有dao层方法
    @Pointcut("execution(* com.haier.cbs.*.dao.*.*(..))")
    public void daoPointcut() {
    }

    /**
     * 根据Dao的数据源注解注入数据源
     * {@link RoutingDataSource} 优先级低于{@link Slave}和{@link Master}
     * {@link RoutingDataSource} 暂时不支持按数据源标识选择数据源,所以现在的版本中此此注解可以忽略
     * @param joinPoint joinPoint
     */
    @Before("daoPointcut()")
    public void before(JoinPoint joinPoint) {
        Object target = joinPoint.getTarget();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Class clazz = target.getClass();

        String routingDataSourceType;

        Map methodSourceMap;
        String className;
        Class[] interfaces = clazz.getInterfaces();
        if (null != interfaces && interfaces.length > 0) {
            className = clazz.getInterfaces()[0].getName();
        } else {
            className = clazz.getName();
        }
        String methodName = method.getName();
        if (classMethodSourceMap.containsKey(className)) {
            methodSourceMap = classMethodSourceMap.get(className);
        } else {
            methodSourceMap = new ConcurrentHashMap();
            classMethodSourceMap.put(className, methodSourceMap);
        }
        if (methodSourceMap.containsKey(methodName)) {
            routingDataSourceType = methodSourceMap.get(methodName);
        } else {
            Master master;
            Slave slave;

            master = AnnotationUtils.findAnnotation(method, Master.class);
            slave = AnnotationUtils.findAnnotation(method, Slave.class);

            if (master == null && slave == null) {
                master = AnnotationUtils.findAnnotation(clazz, Master.class);
                slave = AnnotationUtils.findAnnotation(clazz, Slave.class);
            }

            if (master != null && slave != null) {
                throw new IllegalArgumentException("不能同时指定MASTER和SLAVE");
            }

            routingDataSourceType = master == null ? slave == null ? null
                : RoutingDataSourceType.SLAVE.toString() : RoutingDataSourceType.MASTER.toString();

            if (routingDataSourceType == null) {
                RoutingDataSource routingDataSource;
                routingDataSource = AnnotationUtils.findAnnotation(method, RoutingDataSource.class);
                if (routingDataSource == null) {
                    routingDataSource = AnnotationUtils.findAnnotation(clazz,
                        RoutingDataSource.class);
                }

                if (routingDataSource != null) {
                    routingDataSourceType = routingDataSource.type().toString();
                }

            }

            if (routingDataSourceType == null) {
                routingDataSourceType = RoutingDataSourceType.MASTER.toString();
            }

            methodSourceMap.put(methodName, routingDataSourceType);
        }
        //将选用的数据源放到ThreadLocal中
        ReadWriteRoutingDataSourceHolder.put(routingDataSourceType);

        if (LOGGER.isDebugEnabled()) {
            String interfaceName = clazz.getInterfaces().length > 0 ? clazz.getInterfaces()[0]
                .getName() : "UNKNOWN";
            LOGGER.debug("***** [" + interfaceName + "." + method.getName()
                         + "] route DataSource to " + routingDataSourceType + " *****");
        }
    }

    @After("daoPointcut()")
    public void after(JoinPoint joinPoint) {
        ReadWriteRoutingDataSourceHolder.clear();
        if (LOGGER.isDebugEnabled()) {
            Object target = joinPoint.getTarget();
            Class clazz = target.getClass();
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            String interfaceName = clazz.getInterfaces().length > 0 ? clazz.getInterfaces()[0]
                .getName() : "UNKNOWN";
            LOGGER.debug("***** [" + interfaceName + "." + method.getName() + "] cleared  *****");
        }
    }
}

你可能感兴趣的:(Mybatis+Spring 多数据源方案)