美丽的爪哇岛

基于SpringBoot的Mybatis多数据源访问

2018-05-20  本文已影响172人  begonia_rich

最近一段时间一直在搞多数据访问的功能,这里稍微记录一下,之前搞得都是基于Mybatis的SqlSessionFactory.
最近学习了DataSource做代理,从Mybatis介入选择不同DataSource实现多数据源,跟以前比相对简单灵活多了,缺点就是不支持分布式事务.

源码在最下面供参考

前言

分别讲一下基于DataSource的拓展的思路与实现和基于SqlSessionFactory的思路与实现最后比较一下各自优缺点.

基于DataSource的实现

思路:这里通过DataSource做拓展思路相对简单粗暴,就是在获取链接时我们根据TheadLocal进行判断看选择的是哪一个数据库,这时调用不同的数据库获得不同的链接这样就集成了多数据源

实现

首先实现一个MultiDataSource,它代理了所有其他的DataSource


MultiDataSource

然后在配置时将MultiDataSource设置为主数据源即可


配置MultiDataSource

在接入Mybatis时有两种方案,第一种是基于Mapper接口的形式,第二种是基于SqlSession的操作形式,两者没有太大区分,只不过在实现细节上稍有不同,总的来说Mapper形式更简单,SqlSession的形式更灵活我两种形式都写了一下Demo,这里稍微截图看一下,源码在最后给出.

基于Mapper的形式

/**
 * @author xiezhengchao
 * @since 2018/5/20 15:24
 */
@Mapper
public interface UserMapper {

    @Select("select * from user where id=#{0}")
    @SwitchDataSource("test1")
    User selectByPrimaryKeyTest1(Long id);

    @Select("select * from user where id=#{0}")
    @SwitchDataSource("test2")
    User selectByPrimaryKeyTest2(Long id);

}

/**
 * @author xiezhengchao
 * @since 2018/5/20 15:51
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SwitchDataSource {

    /**
     * db key choose
     */
    String value();

}

/**
 * @author xiezhengchao
 * @since 2018/5/20 15:52
 */
public class MapperFactoryBeanExt<T> extends MapperFactoryBean<T> {

    public MapperFactoryBeanExt() {
    }

    public MapperFactoryBeanExt(Class<T> mapperInterface) {
        super(mapperInterface);
    }

    @Override
    @SuppressWarnings("all")
    public T getObject() throws Exception {
        T proxy = super.getObject();
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[] { getObjectType() },
                new MapperHandle(proxy));
    }

    private class MapperHandle implements InvocationHandler {

        private T originProxy;

        MapperHandle(T originProxy) {
            this.originProxy = originProxy;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            SwitchDataSource switchDataSource = method.getAnnotation(SwitchDataSource.class);
            if (switchDataSource != null) {
                DataSourceSelector.set(switchDataSource.value());
            }
            return method.invoke(originProxy, args);
        }
    }
}

下面看一下运行效果


运行结果

注意@MapperScan的factoryBean参数我们替换为自己的FactoryBean了,否则就切换不了啦


基于SqlSession的形式


/**
 * 为容器中的Dao生成代理
 * 
 * @author xiezhengchao
 * @since 2018/5/20 18:56
 */
@Component
public class BaseDaoBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (BaseDao.class.isAssignableFrom(AopUtils.getTargetClass(bean))) {
            // 采用cglib做代理,子类更灵活
            return CglibProxy.newInstance(bean);
        }
        return bean;
    }

    private static class CglibProxy implements MethodInterceptor {
        private Object target;

        CglibProxy(Object target) {
            this.target = target;
        }

        static Object newInstance(Object target) {
            CglibProxy cglibProxy = new CglibProxy(target);
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(target.getClass());
            enhancer.setCallback(cglibProxy);
            return enhancer.create();
        }

        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            SwitchDataSource switchDataSource = method.getAnnotation(SwitchDataSource.class);
            if (switchDataSource != null) {
                DataSourceSelector.set(switchDataSource.value());
            }
            return method.invoke(target, args);
        }

    }
}

/**
 * 顶层Dao接口,可以放置一些公共方法
 *
 * @author xiezhengchao
 * @since 2018/5/20 18:57
 */
public interface BaseDao {
    // just flag
}

/**
 * 这里可以实现一些公共方法
 * 
 * @author xiezhengchao
 * @since 2018/5/20 15:24
 */
public abstract class BaseDaoImpl implements BaseDao {

}

/**
 * @author xiezhengchao
 * @since 2018/5/20 17:19
 */
@Component
public class UserDao extends BaseDaoImpl {

    @Resource
    private SqlSession sqlSession;

    @SwitchDataSource("test1")
    public User selectByPrimaryKeyTest1(Long id) {
        return sqlSession.selectOne("selectByPrimaryKey", id);
    }

    @SwitchDataSource("test2")
    public User selectByPrimaryKeyTest2(Long id) {
        return sqlSession.selectOne("selectByPrimaryKey", id);
    }

}

查看运行结果


运行结果

这种方式因为直接操作了SqlSession对象所以更灵活,有更大的拓展空间.能够拓展出自己的继承体系,数据库访问层次体系等.


基于SqlSessionFactory的实现

思路:这个还是比较简单的,我们想要JTA的DataSource来管理数据源,那么我们的拓展层面就要放到Mybatis而不能去动DataSource了,所以就是将不同的Dao类进行分包,生成不同的SqlSessionFactory,然后生成多个SqlSession最后通过代理进行路由选择.

实现就不贴了,比较简单,有兴趣可以直接去看源码,这里给出以下SqlSession的代理实现部分,相对简单.


多个SqlSession的代理选择

总结

就目前使用过的几个多数据源访问来说核心的切入点有DataSource或者SqlSession.主要思路都是前置进行注解设置数据源,后面再某个面做动态代理设置到ThreadLocal中然后在下层根据ThreadLocal中不同的值进行切换数据源.
注意这里的数据源可以是DataSource也可以是SqlSession.越往上相对越复杂.但是能兼容的功能就越多.

源码

基于DataSource的多数据源实现:https://github.com/znyh113too/mybatis-multi-datasource-support
基于SqlSessionFactory的多数据源实现:https://github.com/znyh113too/bubi-mybatis-spring-boot


over
上一篇下一篇

猜你喜欢

热点阅读