zoukankan      html  css  js  c++  java
  • PageHelper不安全的分页问题,导致ParserException: syntax error, error in :'it 1 LIMIT ? ', expect LIMIT, actual LIMIT pos , line , column , token LIMIT

    背景

    项目中使用PageHlper插件进行分页,今日发现有多处SQL查询语句都出现了如下的报错。

    com.alibaba.druid.sql.parser.ParserException: syntax error, error in :'it 1 LIMIT ? ', expect LIMIT, actual LIMIT pos 249, line 12, column 16, token LIMIT
    at com.alibaba.druid.sql.parser.SQLParser.printError(SQLParser.java:284)
    at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(
    at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(
    at com.alibaba.druid.sql.SQLUtils.format(SQLUtils.java:255)
    at com.alibaba.druid.filter.logging.LogFilter.statement_executeErrorAfter(LogFilter.java:767)
    at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(
    at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3407)
    at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(
    at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3407)
    at com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.execute(PreparedStatementProxyImpl.java:167)
    at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:498)
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:63)
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)
    at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63)
    at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)
    at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
    at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:136)
    at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
    at com.sun.proxy.$Proxy467.query(Unknown Source)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:77)
    at sun.reflect.GeneratedMethodAccessor239.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
    at com.sun.proxy.$Proxy137.selectOne(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:166)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:82)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
    at com.sun.proxy.$Proxy243.getOneSomthing(Unknown Source)
    at com.lingyejun.project.impl.GetOneThingServiceImpl.getOneThingFromDb(GetOneThingServiceImpl.java:23)
    

    调查

    我们检视堆栈信息发现有一行关键信息,在进行查询的的时候使用的PageHelper进行了拦截。

    at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:136)

    但是看SQL语句并未发现有分页的代码,而且报错的不止这一处,还有其他几个地方,查看提交记录发现最近都没有改动。

    我们想到那肯定是因为其他地方有改动导致的。调查原因后发现有一处代码在调用了PageHelper.startPage后直接返回了,导致的报错,大致代码如下。

    package com.lingyejun.authenticator;
    import com.github.pagehelper.PageHelper;
    import org.springframework.stereotype.Service;
    import javax.annotation.Resource;
    
    @Service
    public class PageHelperTest {
    
        @Resource
        private TeacherMapper teacherMapper;
    
        @Resource
        private StudentMapper studentMapper;
    
        public void doQueryByPage(byte type) {
            PageHelper.startPage(1,10);
    
            if (type == 1){
                studentMapper.query();
            }else if (type ==2){
                teacherMapper.query();
            }
            // 如果type不是1或者2那么此方法执行完是没有释放和清理page变量
            // 会导致其他地方的查询语句报错,或者结果与预期不符
            return;
        }
    
    }

    原理

    PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的。只要我们保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,这就是安全的。因为 PageHelper 在 finally 代码段中自动清除了 ThreadLocal 存储的对象。

    一次PageHelper的分页过程如下

    1. 设置 page 参数
    2. 执行 query 方法
    3. Interceptor 接口 中校验 ThreadLocal 中是否存在有设置的 page 参数
    4. 存在 page 参数,重新生成 count sql 和 page sql,并执行查询。不存在 page 参数,直接返回 查询结果
    5. 执行 LOCAL_PAGE.remove() 清除 page 参数

    但是如果使用线程池的话,当前线程执行完毕,并不会被销毁,而是会将当前线程再次存放到池中,标记为空闲状态,以便后续使用。在后续使用这个线程的时候,由于 线程 的 threadLocals 依旧存在有值,尽管我们在第 1 步时未设置 page 参数,第 3 步 的也能获取到page参数,从而生成 count sql 和 page sql,从而影响我们的正常查询。 

    解决

    以上问题属于人为bug,没有考虑到type为其他值的情况,即出现else时缺少后续逻辑处理,会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。所以我们把对应的逻辑进行调整修改即可, 将else if改成else即可解决这个问题。

    本篇文章如有帮助到您,请给「翎野君」点个赞,感谢您的支持。

    原文链接:https://www.cnblogs.com/lingyejun/p/15675208.html

    作者:翎野君
    出处:http://www.cnblogs.com/lingyejun/
    若本文如对您有帮助,不妨点击一下右下角的【推荐】。
    如果您喜欢或希望看到更多我的文章,可扫描二维码关注我的微信公众号《翎野君》。
    转载文章请务必保留出处和署名,否则保留追究法律责任的权利。
  • 相关阅读:
    关于这个 blog
    P6499 [COCI2016-2017#2] Burza 题解
    CF1172F Nauuo and Bug 题解
    CF1479D Odd Mineral Resource 题解
    CF1442E Black, White and Grey Tree 题解
    CF1442D Sum 题解
    CF1025D Recovering BST 题解
    CF1056E Check Transcription 题解
    CF1025F Disjoint Triangles 题解
    红包算法的PHP实现
  • 原文地址:https://www.cnblogs.com/lingyejun/p/15675208.html
Copyright © 2011-2022 走看看