美文网首页美丽的爪哇岛
基于SpringBoot的Mybatis多数据源访问

基于SpringBoot的Mybatis多数据源访问

作者: begonia_rich | 来源:发表于2018-05-20 19:28 被阅读172次

    最近一段时间一直在搞多数据访问的功能,这里稍微记录一下,之前搞得都是基于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

    相关文章

      网友评论

        本文标题:基于SpringBoot的Mybatis多数据源访问

        本文链接:https://www.haomeiwen.com/subject/egutjftx.html