關於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的源碼吧。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM