zoukankan      html  css  js  c++  java
  • SpringBoot集成PageHelper时出现“在系统中发现了多个分页插件,请检查系统配置!”

    近日在项目中使用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. }

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

  • 相关阅读:
    循环处理
    XMLHttpRequest 加载进度
    createjs 的 bitmapdata类
    console打印数组object具体内容
    html5 粒子组合成logo 的制作思路及方法
    createjs 更新
    css取消input、select默认样式(手机端)
    js获取url参数 兼容某些带#url
    Adobe Edge Animate CC 不再开发更新!
    《FLASH CC 2015 CANVAS 中文教程》——3、this关键字 入门
  • 原文地址:https://www.cnblogs.com/jpfss/p/9723828.html
Copyright © 2011-2022 走看看