美文网首页
从PaginationInterceptor分页实现过程看myb

从PaginationInterceptor分页实现过程看myb

作者: 白菜404 | 来源:发表于2021-05-26 19:16 被阅读0次

    前言:懒惰的我在项目中配置的mybatis-plus PaginationInterceptor分页插件突然失效了,在网上搜了大量文章还是没找到根本原因,只能把mybatis插件加载源码一撸到底了。

    1、Mybatis-plus PaginationInterceptor加载原理源码解析

    mybatis-plus PaginationInterceptor加载顺序:

    源码分析:

    1)MybatisPlusAutoConfiguration类,主要用来自动装配实例化SqlSessionFactory类对象

    关键方法:sqlSessionFactory实例化sqlSessionFactory并加载所有mybatis plugins插件(包括分页page插件)

    黄色代码为把所有mybatis plugins插件装配到sqlSessionFactory中。

    注意:该实例化方法上添加了@ConditionalOnMissingBean注解,表示只有上下文中没有实例化sqlSessionFactory才会执行,所以当现有系统中自定义了sqlSessionFactory实例化方法,则该方法不会执行。

    Java

    @Bean

    @ConditionalOnMissingBean

    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

    // TODO使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean

        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();

        factory.setDataSource(dataSource);

        factory.setVfs(SpringBootVFS.class);

        if (StringUtils.hasText(this.properties.getConfigLocation())) {

            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));

        }

        applyConfiguration(factory);

        if (this.properties.getConfigurationProperties() != null) {

            factory.setConfigurationProperties(this.properties.getConfigurationProperties());

        }

        if (!ObjectUtils.isEmpty(this.interceptors)) {

            factory.setPlugins(this.interceptors);

        }

    //此处省略一万行代码....

        return factory.getObject();

    }

    2)MybatisSqlSessionFactoryBean类:sqlSessionFactory的工厂类,实际去构建sqlSessionFactory对象和加载plus插件,主要方法代码如下:该方法主要调targetConfiguration.addInterceptor(plugin)把所有已经实例化的mybatis插件装载进MybatisConfiguration类中,MybatisConfiguration类主要是承载了创建SqlSessionFactory对象的所有上下文配置信息。

    Java

    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

        final MybatisConfiguration targetConfiguration;

    //此处省略一万行代码...

        if (!isEmpty(this.plugins)) {

            Stream.of(this.plugins).forEach(plugin -> {

                targetConfiguration.addInterceptor(plugin);

                LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");

            });

        }

    //此处省略一万行代码...

        return sqlSessionFactory;

    }

    3) MybatisConfiguration类继承Configuration类:SqlSessionFactory对象的所有上下文配置信息。该类主要提供了PaginationInterceptor插件的添加和使用等方法:

    Java

    //添加拦截器

    public void addInterceptor(Interceptor interceptor) {

      interceptorChain.addInterceptor(interceptor);

    }

    Java

    //执行sql时,先执行所有sqlSessionFactory的拦截器插件

    @Override

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

        executorType = executorType == null ? defaultExecutorType : executorType;

        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

        Executor executor;

        if (ExecutorType.BATCH == executorType) {

            executor = new MybatisBatchExecutor(this, transaction);

        } else if (ExecutorType.REUSE == executorType) {

            executor = new MybatisReuseExecutor(this, transaction);

        } else {

            executor = new MybatisSimpleExecutor(this, transaction);

        }

        if (cacheEnabled) {

            executor = new MybatisCachingExecutor(executor);

        }

        executor = (Executor) interceptorChain.pluginAll(executor);

        return executor;

    }

    4)InterceptorChain类:这个类用于存储拦截器对象list,并提供添加和调用的方法

    Java

    public class InterceptorChain {

      private final List<Interceptor> interceptors = new ArrayList<>();

    //依次调用拦截器方法,注意这里时for循环调用,即,拦截器调用是有先后顺序的。

      public Object pluginAll(Object target) {

        for (Interceptor interceptor : interceptors) {

          target = interceptor.plugin(target);

        }

        return target;

      }

    //添加拦截器

      public void addInterceptor(Interceptor interceptor) {

        interceptors.add(interceptor);

      }

    //获取所有拦截器

      public List<Interceptor> getInterceptors() {

        return Collections.unmodifiableList(interceptors);

      }

    }

    到这里整个mybatis-plus插件的整个加载过程就已经结束了,我们page分页失效的问题也找到了,因为我们新增了自定义的sqlSessionFactory实例化类,所以没有走到MybatisAutoConfigin类去创建sqlSessionFactory对象了,从而也不存在加载page插件这么一回事了。解决方案如下:

    Java

    @Bean(name = "sqlSessionFactory")

    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource, @Autowired PaginationInterceptor paginationInterceptor) throws Exception {

        Resource[] resources = new PathMatchingResourcePatternResolver().getResources(DefaultDataSourceConfig.MAPPER_LOCATION);

        final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();

        sessionFactory.setDataSource(dataSource);

        sessionFactory.setMapperLocations(resources);

    //手动把page分页插件加载到sesionFactory中去

        sessionFactory.setPlugins(paginationInterceptor);

    //开启驼峰映射DTO

        Objects.requireNonNull(sessionFactory.getObject()).getConfiguration().setMapUnderscoreToCamelCase(true);

        return sessionFactory.getObject();

    }

    值得注意的一点是,mybatis-plus sqlSessionFactory级别插件的加载是跟sqlSessionFactory的自动装备强耦合在一起的,从而会导致其他自定义sqlSessionFactory不能自动装配mybatis-plus的插件,需要在自定义sqlSessionFactory中手动添加。改进方案其实可以借鉴Pagehepler分页插件的加载实现原理,使插件和sesionFactory的创建结偶,这样自定义sqlSessionFactory实例化类也能支持自动装载插件了。

    附上PageHelper插件装载源码:

    Java

    @Configuration

    @ConditionalOnBean(SqlSessionFactory.class)

    @EnableConfigurationProperties(PageHelperProperties.class)

    @AutoConfigureAfter(MybatisAutoConfiguration.class)

    public class PageHelperAutoConfiguration {

    //获取到所有sqlSessionFactory list

        @Autowired

        private List<SqlSessionFactory> sqlSessionFactoryList;

        @Autowired

        private PageHelperProperties properties;

        /**

    *接受分页插件额外的属性

         *

         * @return

         */

        @Bean

        @ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)

        public Properties pageHelperProperties() {

            return new Properties();

        }

    //装载pageHepler插件

        @PostConstruct

        public void addPageInterceptor() {

            PageInterceptor interceptor = new PageInterceptor();

            Properties properties = new Properties();

    //先把一般方式配置的属性放进去

            properties.putAll(pageHelperProperties());

    //在把特殊配置放进去,由于close-conn 利用上面方式时,属性名就是 close-conn 而不是 closeConn,所以需要额外的一步

            properties.putAll(this.properties.getProperties());

            interceptor.setProperties(properties);

            for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {

    //对所有的sqlSessionFactory装载pagehepler插件

                sqlSessionFactory.getConfiguration().addInterceptor(interceptor);

            }

        }

    }

    2、Mybatis-plus sqlSessionFactory执行插件源码解析

    前面忽略一万步,感兴趣的同学可以自行沿着插件执行的源码往上扩展查看整个sqlSessionFactory是如何执行的,这里我们主要看sqlSessionFactory执行sql时,如何去执行插件的这一块

    主要类及方法:

    MybatisConfiguration:sqlSessionFactory上下文类。前文其实已经有提到了,该类主要提供了插件的添加和使用的方法。在sqlSessionFactory执行sql时,会调用newExecutor方法来调用依次调用所有的插件。

    Java

    //执行sql时,先执行所有sqlSessionFactory的拦截器插件

    @Override

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

        executorType = executorType == null ? defaultExecutorType : executorType;

        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

        Executor executor;

        if (ExecutorType.BATCH == executorType) {

            executor = new MybatisBatchExecutor(this, transaction);

        } else if (ExecutorType.REUSE == executorType) {

            executor = new MybatisReuseExecutor(this, transaction);

        } else {

            executor = new MybatisSimpleExecutor(this, transaction);

        }

        if (cacheEnabled) {

            executor = new MybatisCachingExecutor(executor);

        }

        executor = (Executor) interceptorChain.pluginAll(executor);

        return executor;

    }

    InterceptorChain类:pluginAll方法依次去调用了所有拦截器的plugin方法

    Java

    public class InterceptorChain {

      private final List<Interceptor> interceptors = new ArrayList<>();

    //依次调用拦截器方法,注意这里时for循环调用,即,拦截器调用是有先后顺序的。

      public Object pluginAll(Object target) {

        for (Interceptor interceptor : interceptors) {

    //依次调用拦截器的plugin方法

          target = interceptor.plugin(target);

        }

        return target;

      }

    //添加拦截器

      public void addInterceptor(Interceptor interceptor) {

        interceptors.add(interceptor);

      }

    //获取所有拦截器

      public List<Interceptor> getInterceptors() {

        return Collections.unmodifiableList(interceptors);

      }

    }

    这里我们还是围绕着PaginationInterceptor分页插件来看具体调用

    PaginationInterceptor类:mybatis-plus分页插件类

    主要方法:

    生成代理类

    Java

    @Override

    public Object plugin(Object target) {

        if (target instanceof StatementHandler) {

            return Plugin.wrap(target, this);

        }

        return target;

    }

    TypeScript

    //生成代理类

    public static Object wrap(Object target, Interceptor interceptor) {

      Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);

      Class<?> type = target.getClass();

      Class<?>[] interfaces = getAllInterfaces(type, signatureMap);

      if (interfaces.length > 0) {

        return Proxy.newProxyInstance(

            type.getClassLoader(),

            interfaces,

            new Plugin(target, interceptor, signatureMap));

      }

      return target;

    }

    //生成代理类具体实现的方法

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

      try {

        Set<Method> methods = signatureMap.get(method.getDeclaringClass());

        if (methods != null && methods.contains(method)) {

    //执行拦截器中的intercept方法

          return interceptor.intercept(new Invocation(target, method, args));

        }

        return method.invoke(target, args);

      } catch (Exception e) {

        throw ExceptionUtil.unwrapThrowable(e);

      }

    }

    Java

    @Override

    public Object intercept(Invocation invocation) throws Throwable {

        StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());

        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

    // SQL解析

        this.sqlParser(metaObject);

    //先判断是不是SELECT操作  (2019-04-10 00:37:31 跳过存储过程)

        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

        if (SqlCommandType.SELECT != mappedStatement.getSqlCommandType()

            || StatementType.CALLABLE == mappedStatement.getStatementType()) {

            return invocation.proceed();

        }

    //针对定义了rowBounds,做为mapper接口方法的参数

        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");

        Object paramObj = boundSql.getParameterObject();

    //判断参数里是否有page对象

        IPage<?> page = null;

        if (paramObj instanceof IPage) {

            page = (IPage<?>) paramObj;

        } else if (paramObj instanceof Map) {

            for (Object arg : ((Map<?, ?>) paramObj).values()) {

                if (arg instanceof IPage) {

                    page = (IPage<?>) arg;

                    break;

                }

            }

        }

        /*

    *不需要分页的场合,如果 size 小于 0 返回结果集

         */

        if (null == page || page.getSize() < 0) {

            return invocation.proceed();

        }

        if (this.limit > 0 && this.limit <= page.getSize()) {

    //处理单页条数限制

            handlerLimit(page);

        }

        String originalSql = boundSql.getSql();

        Connection connection = (Connection) invocation.getArgs()[0];

        if (page.isSearchCount() && !page.isHitCount()) {

            SqlInfo sqlInfo = SqlParserUtils.getOptimizeCountSql(page.optimizeCountSql(), countSqlParser, originalSql);

            this.queryTotal(sqlInfo.getSql(), mappedStatement, boundSql, page, connection);

            if (page.getTotal() <= 0) {

                return null;

            }

        }

        DbType dbType = Optional.ofNullable(this.dbType).orElse(JdbcUtils.getDbType(connection.getMetaData().getURL()));

        IDialect dialect = Optional.ofNullable(this.dialect).orElse(DialectFactory.getDialect(dbType));

        String buildSql = concatOrderBy(originalSql, page);

        DialectModel model = dialect.buildPaginationSql(buildSql, page.offset(), page.getSize());

        Configuration configuration = mappedStatement.getConfiguration();

        List<ParameterMapping> mappings = new ArrayList<>(boundSql.getParameterMappings());

        Map<String, Object> additionalParameters = (Map<String, Object>) metaObject.getValue("delegate.boundSql.additionalParameters");

        model.consumers(mappings, configuration, additionalParameters);

        metaObject.setValue("delegate.boundSql.sql", model.getDialectSql());

        metaObject.setValue("delegate.boundSql.parameterMappings", mappings);

        return invocation.proceed();

    }

    相关文章

      网友评论

          本文标题:从PaginationInterceptor分页实现过程看myb

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