mybatis Invalid bound statement (not found)

    项目使用了mybatis,版本为mybatis-3.2.2.jar + mybatis-spring-1.2.0.jar。今天发现项目运行中出现Invalid bound statement (not found)异常。

    mybatis在启动的时候会扫描mapper,把mapper类中的方法及对应的statement语句注册到Configuration对象中。既然出现了Invalid bound statement (not found)异常,说明在运行过程中没有找到对应mapper中的方法。我们使用的是注解方式,看了对应mapper的方法,注解都有了,没问题,关联了下源码,debug的时候发现,对应mapper中有部分方法是注册了的,而报错的方法没有被注册,怀疑是代码没编译,重新编译过后,问题还在,猜测可能是mybatis在注册的时候出错了,导致后续方法没有被注册。看了下源码,mybatis在Configuration类的getMapper方法中寻找statement,那肯定会有个地方在初始化的时候注册那些statement,在Configuration类中找到相似方法public <T> void addMapper(Class<T> type),追踪进去,这个方法调用了org.apache.ibatis.binding.MapperRegistry类的addMapper方法,代码如下:

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

 追踪进入org.apache.ibatis.builder.annotation.MapperAnnotationBuilder类的parse方法

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          parseStatement(method);
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

 可以看到在这边获取了Mapper接口的所有方法,解析方法上的注解,继续追踪,进入parseStatement方法

void parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      Options options = method.getAnnotation(Options.class);
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = "id";
      String keyColumn = null;
      if (SqlCommandType.INSERT.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else {
          if (options == null) {
            keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
          } else {
            keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
            keyProperty = options.keyProperty();
            keyColumn = options.keyColumn();
          }
        }
      } else {
        keyGenerator = new NoKeyGenerator();
      }

      if (options != null) {
        flushCache = options.flushCache();
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        resultSetType = options.resultSetType();
      }

      String resultMapId = null;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {
        resultMapId = resultMapAnnotation.value();
      } else if (isSelect) {
        resultMapId = parseResultMap(method);
      }

      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          null,                             // ParameterMapID
          parameterTypeClass,
          resultMapId,    // ResultMapID
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          false, // TODO issue #577
          keyGenerator,
          keyProperty,
          keyColumn,
          null,
          languageDriver);
    }
  }

 方法第三行,获取sqlSource,debug到这边的时候有出错(不是报 Invalid bound statement (not found)的犯法),去看了下报错的方法,原来报错的方法使用的是@UpdateProvider注解,但是属性type对应的类中没有method指定的方法,所以在执行代码

getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);

时报了,但是项目启动的时候没错误日志,正常启动成功了,再看了下代码

private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
    try {
      Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
      Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
      if (sqlAnnotationType != null) {
        if (sqlProviderAnnotationType != null) {
          throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
        }
        Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
        final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
        return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
      } else if (sqlProviderAnnotationType != null) {
        Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
        return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
      }
      return null;
    } catch (Exception e) {
      throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
    }
  }

 getSqlSourceFromAnnotations方法中会抛出BuilderException,而在之前的parse方法中捕获的是IncompleteElementException

for (Method method : methods) {
        try {
          parseStatement(method);
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }

 IncompleteElementException是BuilderException的子类,所以这边并不会被捕获,会继续往上抛,导致循环退出,mapper中的后续方法跳过注册,因此在代码执行的时候没有找到对应的statement。

唉,真是坑爹啊。

猜你喜欢

转载自ywu.iteye.com/blog/2306933