zoukankan      html  css  js  c++  java
  • 关于Mapper.xml无效的问题

    昨天在新建Springboot启动后,发现执行相关的SQL报错 org.apache.ibatis.binding.BindingException: Invalid bound statement,具体报错信息如下:

     1 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.hqtc.live.admin.common.dao.UserDao.list
     2     at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:227)
     3     at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:49)
     4     at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:65)
     5     at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:58)
     6     at com.sun.proxy.$Proxy89.list(Unknown Source)
     7     at com.hqtc.live.admin.common.service.impl.UserServiceImpl.list(UserServiceImpl.java:47)
     8     at com.hqtc.live.admin.common.service.impl.UserServiceImpl$$FastClassBySpringCGLIB$$eed42660.invoke(<generated>)
     9     at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    10     at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
    11     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    12     at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    13     at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
    14     at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    15     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    16     at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
    17     at com.hqtc.live.admin.common.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$137fcb94.list(<generated>)
    18     at com.hqtc.live.admin.common.shiro.UserRealm.doGetAuthenticationInfo(UserRealm.java:46)
    19     at org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticatingRealm.java:568)
    20     at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)
    21     at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:267)
    22     at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
    23     at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
    24     at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:270)
    25     at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:256)
    26     at org.apache.shiro.web.filter.authc.AuthenticatingFilter.executeLogin(AuthenticatingFilter.java:53)
    27     at org.apache.shiro.web.filter.authc.FormAuthenticationFilter.onAccessDenied(FormAuthenticationFilter.java:154)
    28     at org.apache.shiro.web.filter.AccessControlFilter.onAccessDenied(AccessControlFilter.java:133)
    29     at org.apache.shiro.web.filter.AccessControlFilter.onPreHandle(AccessControlFilter.java:162)
    30     at org.apache.shiro.web.filter.PathMatchingFilter.isFilterChainContinued(PathMatchingFilter.java:203)
    31     at org.apache.shiro.web.filter.PathMatchingFilter.preHandle(PathMatchingFilter.java:178)
    32     at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:131)
    33     at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    34     at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
    35     at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
    36     at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
    37     at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
    38     at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
    39     at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
    40     at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
    41     at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    42     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    43     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    44     at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
    45     at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    46     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    47     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    48     at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:108)
    49     at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    50     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    51     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    52     at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
    53     at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    54     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    55     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    56     at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
    57     at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    58     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    59     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    60     at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
    61     at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    62     at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478)
    63     at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
    64     at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
    65     at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
    66     at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
    67     at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
    68     at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    69     at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    70     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
    71     at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    72     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    73     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    74     at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    75     at java.lang.Thread.run(Thread.java:745)
    View Code

    我在网上找了好多相关的blog,好多说是

      1:Mapper.xml中的namespace不对应和mapper接口不对应

      2:Mapper.xml中的方法(即id)和mapper接口中的方法名字不同或对应的方法不存在

      3:返回类型不匹配(即没有正确配置ResultMap或者ResultType)

      4:可能xml文件有缓存或者修改后没保存

      5:可能没有配置MapperScan导致dao方法没有被扫描注入

      6:配置文件中mybatis.mapper-locations或mybatis.typeAliasesPackage配置不正确

    我逐条对比测试,发现不是上述的这些问题,那么问题出在哪里呢?

    通过报错信息的第二行,我找到了MapperMethod.SqlCommand方法,具体代码如下:

     1     public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
     2       final String methodName = method.getName();
     3       final Class<?> declaringClass = method.getDeclaringClass();
     4       MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
     5           configuration);
     6       if (ms == null) {
     7         if (method.getAnnotation(Flush.class) != null) {
     8           name = null;
     9           type = SqlCommandType.FLUSH;
    10         } else {
    11           throw new BindingException("Invalid bound statement (not found): "
    12               + mapperInterface.getName() + "." + methodName);
    13         }
    14       } else {
    15         name = ms.getId();
    16         type = ms.getSqlCommandType();
    17         if (type == SqlCommandType.UNKNOWN) {
    18           throw new BindingException("Unknown execution method for: " + name);
    19         }
    20       }
    21     }
    View Code

    通过分析我们得出进入了ms==null的逻辑,因此我们分析MapperMethod.resolveMappedStatement这个方法,看为什么这个sql对应的MappedStatement为什么为空,分析其代码如下

     1     private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
     2         Class<?> declaringClass, Configuration configuration) {
     3       String statementId = mapperInterface.getName() + "." + methodName;
     4       if (configuration.hasStatement(statementId)) {
     5         return configuration.getMappedStatement(statementId);
     6       } else if (mapperInterface.equals(declaringClass)) {
     7         return null;
     8       }
     9       for (Class<?> superInterface : mapperInterface.getInterfaces()) {
    10         if (declaringClass.isAssignableFrom(superInterface)) {
    11           MappedStatement ms = resolveMappedStatement(superInterface, methodName,
    12               declaringClass, configuration);
    13           if (ms != null) {
    14             return ms;
    15           }
    16         }
    17       }
    18       return null;
    19     }
    20   }
    View Code

    其中configuration.hasStatement(statementId)会判断有没有这个sql对应的方法。分析发现,org.apache.ibatis.session.Configuration方法中的属性 mappedStatements 为空。这就不难解释为什么报找不到方法的错误了。

    那么为什么mappedStatements为空呢,我怀疑是mybatis.mapper-locations属性没有被正确加载解析,导致无法正确的找到每个接口方法对应的sql,那么接下来分析mybatis.mapper-locations有没有被加载。Mybatis相关的配置信息被加载后都存在MybatisProperties类中,通过调试查看这个类的属性发现,mybatis.mapper-locations已经被加载了。那么接下来看有没有被解析。我们找到了MybatisAutoConfiguration.sqlSessionFactory方法,具体代码如下:

     1   @Bean
     2   @ConditionalOnMissingBean
     3   public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
     4     SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
     5     factory.setDataSource(dataSource);
     6     factory.setVfs(SpringBootVFS.class);
     7     if (StringUtils.hasText(this.properties.getConfigLocation())) {
     8       factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
     9     }
    10     Configuration configuration = this.properties.getConfiguration();
    11     if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
    12       configuration = new Configuration();
    13     }
    14     if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
    15       for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
    16         customizer.customize(configuration);
    17       }
    18     }
    19     factory.setConfiguration(configuration);
    20     if (this.properties.getConfigurationProperties() != null) {
    21       factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    22     }
    23     if (!ObjectUtils.isEmpty(this.interceptors)) {
    24       factory.setPlugins(this.interceptors);
    25     }
    26     if (this.databaseIdProvider != null) {
    27       factory.setDatabaseIdProvider(this.databaseIdProvider);
    28     }
    29     if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
    30       factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    31     }
    32     if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
    33       factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    34     }
    35     if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
    36       factory.setMapperLocations(this.properties.resolveMapperLocations());
    37     }
    38 
    39     return factory.getObject();
    40   }
    View Code

    在最后你是不是看到了

    1 if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
    2       factory.setMapperLocations(this.properties.resolveMapperLocations());
    3     }

    按道理来说,这个方法是被@Bean注解修饰的,应该会被Spring自动注入,一单被自动注入,那么相应的会执行这个方法。但是通过调试发现,这个方法没有被执行,难怪mybatis.mapper-locations属性没有被正确解析。我突然想到,我们当时在做mysql主从的时候,在相关的配置类中自定义过 SqlSessionFactory 的实现和注入,有可能是这个问题。那么我尝试将我们自定义的SqlSessionFactory的实现给注释掉,重启后发现问题解决了。我们看下 MybatisAutoConfiguration 中关于 sqlSessionFactory 的注入代码部分如下:

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

    我们看到了 @ConditionalOnMissingBean 这个注解,它的意思是如果BeanFactory中存在了 SqlSessionFactory,就不再注入当前的 SqlSessionFactory。这也就是我们自定义SqlSessionFactory的注入方法后,就不在执行这个注入代码的原因。

    最后发现其实问题很简单,就是我们自定义的SqlSessionFactory 的注入覆盖了mybatis内部的注入方法,导致mybatis.mapper-locations没有被解析生效。

    最后总结:

    1:重视报错信息,通过报错信息我们往往能分析到问题产生的直接原因。

    2:找到问题间的关联。就像上述问题,如果你熟悉mybatis的源码,你可能会很快的找到根本原因。如果你不熟悉源码,可以通过断点调试等方法,找到相关类和方法间的引用关系从而找到问题的关键。

    3:还是多看看mybatis的源码吧。

  • 相关阅读:
    【leetcode】49. 字母异位词分组
    【leetcode】48. 旋转图像
    【leetcode】48. 全排列 2
    Day4前端学习之路——背景边框列表链接和更复杂的选择器
    Day3前端学习之路——CSS基本知识
    Day2前端学习之路——HTML基本知识
    Day1前端学习之路——概述
    Axure实现抽奖转盘(二)
    Axure实现百度登录页面(一)
    线性代数课程较好的资料
  • 原文地址:https://www.cnblogs.com/wanghaoyang/p/11725090.html
Copyright © 2011-2022 走看看