Analyze the cause of BindingException: Invalid bound statement (not found) by tracing the source code

background

The new project, the framework springboot + mybatis + mybatis plus, wants to add a CommonMapper and the corresponding mapper xml file to store general sql or sql across multiple tables. As a result, an error is reported when starting the test, as shown below:

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): 
	at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:235) 
	at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.<init>(MybatisMapperMethod.java:50)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.lambda$cachedMapperMethod$0(MybatisMapperProxy.java:101)
	at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.cachedMapperMethod(MybatisMapperProxy.java:100)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:95)
	at com.sun.proxy.$Proxy157.testList(Unknown Source)
	at com.mogulinker.gqgj.integrate.core.application.controller.DemoController.test(DemoController.java:189)

Troubleshooting process

After careful analysis, I can't understand it. I can see many statements on the Internet, but I don't want to try blindly. I want to find the essential reason, so I have the following troubleshooting process:
First navigate to mappermethod Java: find out where the error came from on line 235, as shown below

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      final String methodName = method.getName();
      final Class<?> declaringClass = method.getDeclaringClass();
    // ==3. Continue to find this method, indicating that the resolveMappedStatement method returns null. Continue to follow up this method
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
          //==2 look up, indicating that ms is empty
        if (method.getAnnotation(Flush.class) != null) {
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
            //  ==1 = ===========================================================================yes, mistakes come out of here========
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
      String statementId = mapperInterface.getName() + "." + methodName;
   
      if (configuration.hasStatement(statementId)) {
          // ===4 description configuration The hasstatement (statementid) method returns false. Continue to look at this method
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {
        return null;
      }
      for (Class<?> superInterface : mapperInterface.getInterfaces()) {
        if (declaringClass.isAssignableFrom(superInterface)) {
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
              declaringClass, configuration);
          if (ms != null) {
            return ms;
          }
        }
      }
      return null;
    }
    ```
```java
public boolean hasStatement(String statementName) {
    return hasStatement(statementName, true);
  }

  public boolean hasStatement(String statementName, boolean validateIncompleteStatements) {
    if (validateIncompleteStatements) {
      buildAllStatements();
    }
      // ==5 indicates that this method returns false. Continue to find
    return mappedStatements.containsKey(statementName);
  }
//==6. It is found that mappedStatements is a map set, indicating that it does not exist in the set. Next, we need to find out when the map is initialized and where the elements are added
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
//==7. Search one and find that there are methods that will add elements to this collection. All the places where this method is called are found to be many for simplicity. Directly make a breakpoint in this method and start the debug mode
public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

==8 from the call chain, you can clearly see where this method is called from, and find it layer by layer,

You can see that the class MyBatisPlusAutoConfiguration is at the bottom of the figure above. Among the methods shown below, the method that returns SqlSessionFactory:

Navigate to the following line:

After seeing the problem here, the problem is basically determined. Only one demoMapper xml file is found here. There should be three in my project. One is a demoMapper automatically generated by the code generation tool, the other is a modulemapper generated according to a table, and the other is the xml corresponding to the CommonMapper I manually added.
That means that the mapper failed to scan the scanning path according to the mapper location. Is it possible to determine the problem of configuring the path? Continue to verify,

As shown in the above figure, all xml files are obtained according to the mapper locations path in the configuration file. The mapper locations configuration is as follows:

mybatis-plus:
  mapper-locations: classpath*:com/aa/bb/cc/data/**/xml/*Mapper.xml

Here you can determine the wildcard configuration file. You can determine it by comparing the paths of the following three xml files:
com/aa/bb/cc/data/xml/DempMapper.xml
com/aa/bb/cc/data/xml/module1/Module1Mapper.xml
com/aa/bb/cc/data/xml/common/CommonMapper.xml
Only DempMapper is scanned, and the wildcard is changed to COM / AA / BB / cc / data / * * / * mapper XML so you can scan it out.

Conclusion:

Unexpectedly, it is because the mapper locations is configured incorrectly.
The reason why I didn't think of it is that the project is directly generated by the company's code generation tool. All paths are generated automatically, and the built-in DemoMapper can work normally, and the Module1Mapper generated by mybatis plus can work half normally. Why is it half normal, This is because the addition, deletion, modification and query methods based on the automatic use of mybatis plus can be used. Even if the new method is written with the @ Select annotation method, the sql statement can be used normally. It will be abnormal only when the sql corresponding to the method is written to the xml file, which is also an error reported above.

Keywords: Java Mybatis

Added by daftdog on Wed, 23 Feb 2022 02:21:18 +0200