背景
我的项目中应用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 LIMITat 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;@Servicepublic 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即可解决这个问题。
本篇文章如有帮忙到您,请给「翎野君」点个赞,感谢您的反对。