近日在项目中使用SpringBoot集成PageHelper后,跑单元测试时出现了“在系统中发现了多个分页插件,请检查系统配置!”这个问题。

如下图所示:


  1. org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
  2. ### Error querying database. Cause: java.lang.RuntimeException: 在系统中发现了多个分页插件,请检查系统配置!
  3. ### Cause: java.lang.RuntimeException: 在系统中发现了多个分页插件,请检查系统配置!
  4. at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77)
  5. at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
  6. at com.sun.proxy.$Proxy109.selectList(Unknown Source)
  7. at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230)
  8. at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:137)
  9. at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:75)
  10. at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
  11. at com.sun.proxy.$Proxy110.selectAll(Unknown Source)
  12. at com.lianjia.cto.ke.broadband.service.StationInfoService.selectPage(StationInfoService.java:27)
  13. at com.lianjia.cto.ke.broadband.service.StationInfoService$$FastClassBySpringCGLIB$$ee2f34a4.invoke(<generated>)
  14. at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
  15. at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
  16. at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
  17. at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52)
  18. at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168)
  19. at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
  20. at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
  21. at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
  22. at com.lianjia.cto.ke.broadband.service.StationInfoService$$EnhancerBySpringCGLIB$$1da3a0f3.selectPage(<generated>)
  23. at com.lianjia.cto.ke.broadband.service.StationInfoServiceTest.selectPage(StationInfoServiceTest.java:27)
  24. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  25. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  26. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  27. at java.lang.reflect.Method.invoke(Method.java:498)
  28. at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
  29. at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
  30. at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
  31. at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
  32. at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
  33. at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
  34. at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
  35. at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
  36. at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
  37. at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
  38. at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
  39. at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
  40. at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
  41. at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
  42. at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
  43. at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
  44. at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
  45. at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
  46. at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
  47. at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
  48. at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
  49. at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
  50. at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
  51. at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
  52. Caused by: org.apache.ibatis.exceptions.PersistenceException:
  53. ### Error querying database. Cause: java.lang.RuntimeException: 在系统中发现了多个分页插件,请检查系统配置!
  54. ### Cause: java.lang.RuntimeException: 在系统中发现了多个分页插件,请检查系统配置!
  55. at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
  56. at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:150)
  57. at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
  58. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  59. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  60. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  61. at java.lang.reflect.Method.invoke(Method.java:498)
  62. at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
  63. ... 46 more
  64. Caused by: java.lang.RuntimeException: 在系统中发现了多个分页插件,请检查系统配置!
  65. at com.github.pagehelper.PageHelper.skip(PageHelper.java:55)
  66. at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:92)
  67. at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
  68. at com.sun.proxy.$Proxy123.query(Unknown Source)
  69. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  70. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  71. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  72. at java.lang.reflect.Method.invoke(Method.java:498)
  73. at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:63)
  74. at com.sun.proxy.$Proxy123.query(Unknown Source)
  75. at com.github.pagehelper.PageInterceptor.executeAutoCount(PageInterceptor.java:201)
  76. at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:113)
  77. at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
  78. at com.sun.proxy.$Proxy123.query(Unknown Source)
  79. at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
  80. ... 52 more

先上结果:maven的pagehelper-spring-boot-starter这个依赖,提供了自动配置分页插件的功能,所以有两种方法解决这个问题

第一种,SpringBoot启动类的注解上排除这个自动配置

@SpringBootApplication(exclude = PageHelperAutoConfiguration.class)

第二种,在构建SqlSessionFactory时,不要再去手动添加分页的拦截器,在application.yml中进行配置pagehelper的属性,在SpringBoot启动时会将配置自动注入生成拦截器


  1. # PageHelper配置
  2. pagehelper:
  3. offsetAsPageNum: true
  4. rowBoundsWithCount: true
  5. reasonable: true
  6. returnPageInfo: true
  7. params: count=countSql

以下是解决问题的思路:

看到这个报错后,查看了一下配置,发现并没有配置多个PageHelper。于是查看了一下报错部分的源码,问题出现在一个skip方法中,如下图所示:


  1. public class PageHelper extends PageMethod implements Dialect {
  2. //...
  3. @Override
  4. public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
  5. //此处的MSUtils.COUNT为一个常量值"_COUNT"
  6. if(ms.getId().endsWith(MSUtils.COUNT)){
  7. throw new RuntimeException("在系统中发现了多个分页插件,请检查系统配置!");
  8. }
  9. //...
  10. }
  11. //...
  12. }

继续向上找,找到了分页插件的拦截器


  1. public class PageInterceptor implements Interceptor {
  2. //...
  3. private String countSuffix = "_COUNT";
  4. @Override
  5. public Object intercept(Invocation invocation) throws Throwable {
  6. try {
  7. //...
  8. //调用方法判断是否需要进行分页,如果不需要,直接返回结果
  9. if (!dialect.skip(ms, parameter, rowBounds)) {
  10. //反射获取动态参数
  11. String msId = ms.getId();
  12. Configuration configuration = ms.getConfiguration();
  13. Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
  14. //判断是否需要进行 count 查询
  15. if (dialect.beforeCount(ms, parameter, rowBounds)) {
  16. String countMsId = msId + countSuffix;
  17. Long count;
  18. //先判断是否存在手写的 count 查询
  19. MappedStatement countMs = getExistedMappedStatement(configuration, countMsId);
  20. if(countMs != null){
  21. count = executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
  22. } else {
  23. countMs = msCountMap.get(countMsId);
  24. //自动创建
  25. if (countMs == null) {
  26. //根据当前的 ms 创建一个返回值为 Long 类型的 ms
  27. countMs = MSUtils.newCountMappedStatement(ms, countMsId);
  28. msCountMap.put(countMsId, countMs);
  29. }
  30. count = executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler);
  31. }
  32. //处理查询总数
  33. //返回 true 时继续分页查询,false 时直接返回
  34. if (!dialect.afterCount(count, parameter, rowBounds)) {
  35. //当查询总数为 0 时,直接返回空的结果
  36. return dialect.afterPage(new ArrayList(), parameter, rowBounds);
  37. }
  38. }
  39. //判断是否需要进行分页查询
  40. if (dialect.beforePage(ms, parameter, rowBounds)) {
  41. //生成分页的缓存 key
  42. CacheKey pageKey = cacheKey;
  43. //处理参数对象
  44. parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
  45. //调用方言获取分页 sql
  46. String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
  47. BoundSql pageBoundSql = new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter);
  48. //设置动态参数
  49. for (String key : additionalParameters.keySet()) {
  50. pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
  51. }
  52. //执行分页查询
  53. resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
  54. } else {
  55. //不执行分页的情况下,也不执行内存分页
  56. resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
  57. }
  58. } else {
  59. //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
  60. resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
  61. }
  62. return dialect.afterPage(resultList, parameter, rowBounds);
  63. } finally {
  64. dialect.afterAll();
  65. }
  66. }
  67. }

可以发现以_COUNT结尾的MappedStatement.id是由分页插件的拦截器自动生成的,而这个由分页插件的生成之后,怎么会又一次的调用skip方法呢?所以猜测是有多个分页插件的拦截器影响到了。于是查阅了一下相关的资料,发现PageHelper引入了SpringBoot的自动配置,以下是自动配置的源码:


  1. @Configuration
  2. @ConditionalOnBean(SqlSessionFactory.class)
  3. @EnableConfigurationProperties(PageHelperProperties.class)
  4. @AutoConfigureAfter(MybatisAutoConfiguration.class)
  5. public class PageHelperAutoConfiguration {
  6. @Autowired
  7. private List<SqlSessionFactory> sqlSessionFactoryList;
  8. @Autowired
  9. private PageHelperProperties properties;
  10. /**
  11. * 接受分页插件额外的属性
  12. *
  13. * @return
  14. */
  15. @Bean
  16. @ConfigurationProperties(prefix = PageHelperProperties.PAGEHELPER_PREFIX)
  17. public Properties pageHelperProperties() {
  18. return new Properties();
  19. }
  20. @PostConstruct
  21. public void addPageInterceptor() {
  22. PageInterceptor interceptor = new PageInterceptor();
  23. Properties properties = new Properties();
  24. //先把一般方式配置的属性放进去
  25. properties.putAll(pageHelperProperties());
  26. //在把特殊配置放进去,由于close-conn 利用上面方式时,属性名就是 close-conn 而不是 closeConn,所以需要额外的一步
  27. properties.putAll(this.properties.getProperties());
  28. interceptor.setProperties(properties);
  29. for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
  30. sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
  31. }
  32. }
  33. }

至此,问题算是比较明白了,是因为多个分页拦截器的作用,导致了该异常的出现,所以全局只要保留一个就可以了。

SpringBoot集成PageHelper时出现“在系统中发现了多个分页插件,请检查系统配置!”的更多相关文章

  1. Linux系统中ElasticSearch搜索引擎安装配置Head插件

    近几篇ElasticSearch系列: 1.阿里云服务器Linux系统安装配置ElasticSearch搜索引擎 2.Linux系统中ElasticSearch搜索引擎安装配置Head插件 3.Ela ...

  2. win7或win2008系统中,出现【已停止工作,联机检查解决方案并关闭该程序,关闭程序】解决方法!

    win7或win2008系统中,出现[已停止工作,联机检查解决方案并关闭该程序,关闭程序]解决方法! 经过摸索,点击[控制面板]-[操作中心]-[更改操作中心设置]-[问题报告设置]-[从不检查解决方 ...

  3. springboot集成PageHelper,支持springboot2.0以上版本

    第一步:pom文件还是需要引入依赖 <!--mybatis的分页插件--> <dependency> <groupId>com.github.pagehelper& ...

  4. 关于SpringBoot集成myBatis时,mapper接口注入失败的问题

    问题描述: 在Spring Boot集成myBatis时,发现启动时,mapper接口一直注入失败. 现象如下: VehicleDAO就是需要的mapper对象,一个简单的接口. 已经在applica ...

  5. springboot集成pagehelper插件

    1.在pom.xml中引入依赖 <dependency> <groupId>com.github.pagehelper</groupId> <artifact ...

  6. C#开发BIMFACE系列39 网页集成开发3:审图系统中三维模型比对

    系列目录     [已更新最新开发文章,点击查看详细] 在建筑施工图审查系统中,设计单位提交设计完成的模型/图纸,审查专家审查模型/图纸.审查过程中如果发现不符合规范的地方,则流程退回到设计单位,设计 ...

  7. C#开发BIMFACE系列38 网页集成开发2:审图系统中的模型或图纸批注

    系列目录     [已更新最新开发文章,点击查看详细] 在运维或协同的场景中,经常需要对模型或图纸进行批注,及时记录已发现的问题并交给相关负责的人员. 在开始实现功能之前,先了解一下BIMFACE中有 ...

  8. 用WIN7系统IIS的提示:数据库连接出错,请检查Conn.asp文件中的数据库参数设置

    我用科讯的从4.0开始,去年开始很少用科讯做新站了,今天拿来做一下,结果悲剧了,数据库路径老是不对,百度一番又一番的,,最后终于给度娘解决了.分享出来给遇到同样的问题的人. 用WIN7系统IIS的注意 ...

  9. spring-boot集成PageHelper和通用Mapper

    前提条件:已经集成mybatis 代码生成步骤: 添加依赖 <dependency> <groupId>tk.mybatis</groupId> <artif ...

随机推荐

  1. 4、python基本知识点及字符串常用方法

    查看变量内存地址   id(变量名) ni = 123 n2 = 123 ni和n2肯定是用的两份内存,但是python对于数字在-5~257之间的数字共用一份地址,范围可以修改 name = ‘李璐 ...

  2. Dynamic device virtualization

    A system and method for providing dynamic device virtualization is herein disclosed. According to on ...

  3. Linear to physical address translation with support for page attributes

    Embodiments of the invention are generally directed to systems, methods, and apparatuses for linear ...

  4. /bin/bash^M: bad interpreter: 没有那个文件或文件夹

    执行脚本时出现了这样一个错误,打开之后并没有找到所谓的^M,查了之后才知道原来是文件格式的问题,也就是linux和windows之间的不全然兼容... 详细细节无论,假设验证: vim test.sh ...

  5. Hadoop读书笔记(四)HDFS体系结构

    Hadoop读书笔记(一)Hadoop介绍:http://blog.csdn.net/caicongyang/article/details/39898629 Hadoop读书笔记(二)HDFS的sh ...

  6. iOS8新特性

    1. App Extension Programming Guide 2.LocalAuthentication.framework - Touch ID Authentication 3.Local ...

  7. Windows Vista下UIPI和Windows消息的故事(UIPI默认情况下会禁止所有高于WM_USER的消息)

    一. 什么是UIPI UIPI指User Interface Privilege Isolation(用户界面特权隔离),是Windows Vista 新引入的一种安全特性.用于拦截接收比自身进程MI ...

  8. bootstrap课程1 bootstrap为什么这么火

    bootstrap课程1 bootstrap为什么这么火 一.总结 一句话总结:响应式,样式多,功能多. 1.bootstrap通过什么药实现响应式? 响应式web布局是让用户通过不同尺寸的浏览器都可 ...

  9. workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的)

    workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的) 一.总结 1.下面链接里面还有一个来聊的php聊天室源码可以学习 2. ...

  10. php websocket-网页实时聊天之PHP实现websocket(ajax长轮询和websocket都可以时间网络聊天室)

    php websocket-网页实时聊天之PHP实现websocket(ajax长轮询和websocket都可以时间网络聊天室) 一.总结 1.ajax长轮询和websocket都可以时间网络聊天室 ...