关于java:PageHelper不安全的分页问题导致ParserException-syntax-error

38次阅读

共计 4161 个字符,预计需要花费 11 分钟才能阅读完成。

背景

我的项目中应用 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 的分页过程如下

设置 page 参数
执行 query 办法
Interceptor 接口 中校验 ThreadLocal 中是否存在有设置的 page 参数
存在 page 参数,从新生成 count sql 和 page sql,并执行查问。不存在 page 参数,间接返回 查问后果
执行 LOCAL_PAGE.remove() 革除 page 参数
然而如果应用线程池的话,以后线程执行结束,并不会被销毁,而是会将以后线程再次寄存到池中,标记为闲暇状态,以便后续应用。在后续应用这个线程的时候,因为 线程 的 threadLocals 仍旧存在有值,只管咱们在第 1 步时未设置 page 参数,第 3 步 的也能获取到 page 参数,从而生成 count sql 和 page sql,从而影响咱们的失常查问。

解决

以上问题属于人为 bug,没有思考到 type 为其余值的状况,即呈现 else 时短少后续逻辑解决,会导致 PageHelper 生产了一个分页参数,然而没有被生产,这个参数就会始终保留在这个线程上。当这个线程再次被应用时,就可能导致不该分页的办法去生产这个分页参数,这就产生了莫名其妙的分页。所以咱们把对应的逻辑进行调整批改即可,将 else if 改成 else 即可解决这个问题。

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

正文完
 0