近日在项目中使用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. amazeui学习笔记--css(HTML元素4)--图片image

    amazeui学习笔记--css(HTML元素4)--图片image 一.总结 1.响应式图片:随着页面宽度而变化 .am-img-responsive class. <img src=&quo ...

  2. Varnish 实战

    Varnish 实战项目   目录 实现基于Keepalived+Haproxy+Varnish+LNMP企业级架构 一.环境准备 1.1 相关配置 1.2 安装服务 1.3 关闭防火墙及selinu ...

  3. 1.3 Python基础知识 - 用户交互及传递参数

    一.用户交互 用户交互方面,每种开发语言都有不同的方式,例如shell语言用的是,“read -p "What is  your name ? " ”.python中是什么样子的呢 ...

  4. express,中间件(body-parser),req.body获取不到参数(含postman发请求的方法)

    问题描述: 最近在做毕设,express 里边的中间件(body-parser)失效,req.body获取不到任何值,req.query能获取到值.一开始加body-parser中间件是有用的,直到昨 ...

  5. SpringBoot 使用yml配置 mybatis+pagehelper+druid+freemarker实例

    SpringBoot 使用yml配置 mybatis+pagehelper+druid+freemarker实例 这是一个简单的SpringBoot整合实例 这里是项目的结构目录 首先是pom.xml ...

  6. tomcat 服务形式检测

    http://blog.chinaunix.net/uid-20449851-id-2369842.html

  7. Java反射学习总结一(基础篇)

    Class类是Reflection API中核心的类,他位于Java.lang.Class 列出一些常用的方法. - getName() : 获得类的完整名字 - getFields() : 获得类的 ...

  8. 【hdu5527】【2015ACM/ICPC亚洲区长春站 】Too Rich

    题目链接: pid=5527">http://acm.hdu.edu.cn/showproblem.php?pid=5527 题意&题解: 感觉自己真是弱啊,自己想的贪心是错的 ...

  9. 使用ionic3快速开发webapp(二)

    本文整理了使用ionic3开发时会用到的一些最基本组件及用法 1.ion-tabs 最常见的通过标签切换页面: tabs.html <ion-tabs> <ion-tab [root ...

  10. Vue.js如何划分组件

    常见的一些页面,大家坐在一起敲代码就可以了,做完这个页面再做别的页面,但是作为一个功能复杂的系统,尤其是使用一些适合模块化开发的框架,这样会显得效率很低,那么我们就单纯的看在Vue里面如何划分组件的. ...