MyBatis 'Invalid bound statement' Issue Caused by Spring Loading Sequence
This was a really weird issue and took me some time to fix.
Background
This is a web application using Spring + MyBatis.
The basic mapper xml files/Java classes are generated using Maven. It worked well.
I added some customized content into the existing mapper xml files/Java classes. It worked well.
I was told that the customized content should not be added the generated mapper xml files/Java classes. Instead, I should create new mapper xml files to contain the DB operation content, and create new Java interface classes which extend the existing generated ones and contain the new added methods. Then I can use the new Java interface classes directly in my code. This is a valid suggestion and I did so.
But the following error happened when Spring starts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Caused by: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.jcloud.blockchain.orm.mapper.EnhancedChannelMapper.selectByExample at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:223) at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:48) at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:59) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:52) at com.sun.proxy.$Proxy41.selectByExample(Unknown Source) at a.b.c.impl.FabricConfigDBHelper.getChannels(FabricConfigDBHelper.java:66) at a.b.c.impl.FabricConfigDBImpl.initChannels(FabricConfigDBImpl.java:82) at a.b.c.impl.FabricConfigDBImpl.initialize(FabricConfigDBImpl.java:54) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:354) at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:305) at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:133) ... 41 more
Solution
First, I confirm the mapper xml files/Java classes I created are all correct. Then I find if I add the autowired mapper instance in code directly, it also works:
Note the “@PostConstruct” annotation. This class needs some initialization after Spring autowiring the dependency. And the “FabricConfigDBHelper” instance is used in the initialization phase where the exception happens. So I doubt the problem is: when the initialization of FabricConfigDBImpl happens, the mapper classes of MyBatis is not fully ready. Instead, it only finishes the basic mapper classes without parsing the parent mapper classes if there is any. So only the methods defined in the child mapper interface class can be recognized but the ones defined in the parent mapper interface cannot. To fix it, lazy initialization should be used: only when the “FabricConfigDBImpl” instance is about to be used, the initialization should be done.
To verify my speculation, I update the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Note the "FabricConfigDBImpl" class implements the "FabricConfig" interface. @Component publicclassFabricConfigFactory {
@Autowired private FabricConfig fabricConfig;
public FabricConfig getInstance(LoginUser user)throws MalformedURLException { if (!fabricConfig.isInitialized()) { fabricConfig.initialize(); } returnthis.fabricConfig; } }
After this change, the initialization of “FabricConfigDBImpl” does not happen during Spring starts. Instead, it’s done when about to be used.