后台工程使用PageHlper插件进行分页。今天发现很多SQL查询语句都有如下报错。com.alibaba.druid.sql.parser.ParserException:语法错误,错误:'it1LIMIT?',期望LIMIT,实际LIMITpos249,第12行,第16列,令牌LIMITatcom.alibaba.druid.sql.parser.SQLParser.printError(SQLParser.java:284)atcom.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(在com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(在com.alibaba.druid.sql.SQLUtils.format(SQLUtils.java:255)在com.alibaba.druid.filter.logging.LogFilter。statement_executeErrorAfter(LogFilter.java:767)在com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(在com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3407)在com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(在com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:3407)在com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.execute(PreparedStatementProxyImpl.java:167)atcom.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:498)在org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:63)在org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)atorg.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63)atorg.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)atorg.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)在org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)在com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:136)atorg.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)atcom.sun.proxy.$Proxy467.query(UnknownSource)atorg.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)在org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)atorg.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:77)atsun.reflect.GeneratedMethodAccessor239.invoke(UnknownSource)atsun.reflect.DelegatingMethodAccessorImpl.Invoke(java:43)在java.lang.reflect.Method.invoke(Method.java:498)在org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)在com.sun.proxy.$Proxy137。在org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:166)在org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:82)在org.apache.ibatis.binding.selectOne(UnknownSource)。MapperProxy.invoke(MapperProxy.java:59)atcom.sun.proxy.$Proxy243.getOneSomthing(UnknownSource)atcom.lingyejun.project.impl.GetOneThingServiceImpl.getOneThingFromDb(GetOneThingServiceImpl.java:23)调查我们检查堆栈信息并找到一行关键信息,在查询时被PageHelper拦截atcom.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:136),但是在sql语句中没有找到分页代码,不仅这个地方报错,其他几个地方也报错。查看提交记录发现最近没有变化。我们认为这一定是因为其他地方发生了变化。排查原因后发现有一段代码调用PageHelper.startPage后直接返回,导致报错。一般代码如下。packagecom.lingyejun.authenticator;importcom.github.pagehelper.PageHelper;importorg.springframework.stereotype.Service;importjavax.annotation.Resource;@ServicepublicclassPageHelperTest{@ResourceprivateTeacherMapperteacherMapper;@Resource私有StudentMapperstudentMapper;publicvoiddoQueryByPage(bytetype){PageHelper.startPage(1,10);如果(类型==1){studentMapper.query();}elseif(type==2){teacherMapper.query();}//如果类型不是1或2,执行完这个方法后页面变量不会被释放清理。//会导致其他查询语句出错,或者返回的结果不是预期的;}}原理PageHelper方法使用静态ThreadLocal参数,分页参数与Threads绑定。只要我们确保MyBatis查询方法紧跟在PageHelper方法调用之后,这是安全的。因为PageHelper在finally代码段会自动清除ThreadLocal存放的对象。一个PageHelper分页流程如下:设置page参数,执行查询方法Interceptor接口,在ThreadLocal中检查是否有page参数,重新生成countsql和pagesql,执行查询。没有page参数,直接返回查询结果,执行LOCAL_PAGE.remove()清除page参数,但是如果使用线程池,执行后不会销毁当前线程,而是重新存入池中,标记为免费供将来使用。后面使用这个线程的时候,由于线程的threadLocals还是有值的,即使我们第一步没有设置page参数,第三步也可以获取到page参数,从而生成countsql和pagesql,影响我们的正常查询。解决上述问题是人为bug,没有考虑type其他值的情况,也就是出现else时缺少后续逻辑处理,会导致PageHelper产生一个分页参数,其实不是消耗掉了,这个参数会一直保留在这个线程上。当再次使用该线程时,可能会导致本不该分页的方法消耗了分页参数,从而导致莫名其妙的分页。所以我们可以调整修改相应的逻辑,把elseif改成else来解决这个问题。如果本文对您有帮助,请给“凌夜君”点个赞,谢谢支持。
