org.apache.ibatis.binding.BindingException: Invalid bound statement (not found):

在项目上遇到如下情况,mapper绑定不成功,通过debug找到问题出在MapperMethod的内部类SqlCommand的构造方法

<configuration>
    <!-- 读取存放连接数据库信息的配置文件 -->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="cacheEnabled" value="true"/>
    </settings>


    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql"/>
    </databaseIdProvider>
    <!-- 注册映射文件 -->
</configuration>

   <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:/conf/mybatis-config.xml"/>
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:/conf/mybatis/mappers/*.xml"/>
    </bean>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test.com.bytedance.dao.EmployeeMapper">

    <select id="getEmps" resultType="test.com.bytedance.bean.Employee" databaseId="mysql">
        select * from employees
    </select>
</mapper>

 加载SqlSessionFactoryBean时,只是将mybatis的资源目录加载过去,并不会给里面的属性赋值,所以databaseprovider并不会被读取填充到相应的属性上去,正确做法应该写在sqlSessionFactoryBean的属性里。

   if (this.databaseIdProvider != null) {
            try {
                targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
            } catch (SQLException var23) {
                throw new NestedIOException("Failed getting a databaseId", var23);
            }
        }

 因为idprovider为空,所以不会执行getdatabaseID方法,顺便看一下getdatabaseID的源码核心部分。实际上是通过connection链接了数据库,去搞了它的元数据(productname)

private String getDatabaseProductName(DataSource dataSource) throws SQLException {
        Connection con = dataSource.getConnection();
        Throwable var3 = null;

        String var5;
        try {
            DatabaseMetaData metaData = con.getMetaData();
            var5 = metaData.getDatabaseProductName();
        } catch (Throwable var14) {
            var3 = var14;
            throw var14;
        } finally {
            if (con != null) {
                if (var3 != null) {
                    try {
                        con.close();
                    } catch (Throwable var13) {
                        var3.addSuppressed(var13);
                    }
                } else {
                    con.close();
                }
            }

        }

        return var5;
    }

 因为获取的databaseid为空,所以上面的mapper文件里指定databaseid为mysql就会找不到匹配的。导致mapper绑定失败。

      protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
      if (this.mapperLocations != null) {
            if (this.mapperLocations.length == 0) {
                LOGGER.warn(() -> {
                    return "Property 'mapperLocations' was specified but matching resources are not found.";
                });
            } else {
                Resource[] var3 = this.mapperLocations;
                int var4 = var3.length;

                for(int var5 = 0; var5 < var4; ++var5) {
                    Resource mapperLocation = var3[var5];
                    if (mapperLocation != null) {
                        try {
                            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                            xmlMapperBuilder.parse();
                        } catch (Exception var19) {
                            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19);
                        } finally {
                            ErrorContext.instance().reset();
                        }

                        LOGGER.debug(() -> {
                            return "Parsed mapper file: '" + mapperLocation + "'";
                        });
                    }
                }
            }
        }
    return this.sqlSessionFactory;
}

 创建xmlmapperbuilder对象

private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        super(configuration);
        this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
        this.parser = parser;
        this.sqlFragments = sqlFragments;
        this.resource = resource;
    }

 接着就是调用xmlmapperbuilder的parse读取xml的node

private void configurationElement(XNode context) {
        try {
            String namespace = context.getStringAttribute("namespace");
            if (namespace != null && !namespace.isEmpty()) {
                this.builderAssistant.setCurrentNamespace(namespace);
                this.cacheRefElement(context.evalNode("cache-ref"));
                this.cacheElement(context.evalNode("cache"));
                this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                this.resultMapElements(context.evalNodes("/mapper/resultMap"));
                this.sqlElement(context.evalNodes("/mapper/sql"));
                this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
            } else {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
        } catch (Exception var3) {
            throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
        }
    }

 xmlmapperbuilder调用buildstatementfromcontext顾名思义你懂得

接着调用xmlparser方法返回所有statement的list

public List<XNode> evalNodes(Object root, String expression) {
        List<XNode> xnodes = new ArrayList();
        NodeList nodes = (NodeList)this.evaluate(expression, root, XPathConstants.NODESET);

        for(int i = 0; i < nodes.getLength(); ++i) {
            xnodes.add(new XNode(this, nodes.item(i), this.variables));
        }

        return xnodes;
    }

 

接下来是发生不匹配的关键

private void buildStatementFromContext(List<XNode> list) {
        if (this.configuration.getDatabaseId() != null) {
            this.buildStatementFromContext(list, this.configuration.getDatabaseId());
        }

        this.buildStatementFromContext(list, (String)null);
    }

因为configuration里没有获取databaseid,所以是空。不执行if里的内容, 

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        Iterator var3 = list.iterator();

        while(var3.hasNext()) {
            XNode context = (XNode)var3.next();
            XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);

            try {
                statementParser.parseStatementNode();
            } catch (IncompleteElementException var7) {
                this.configuration.addIncompleteStatement(statementParser);
            }
        }

    }
public void parseStatementNode() {
        String id = this.context.getStringAttribute("id");
        String databaseId = this.context.getStringAttribute("databaseId");
        if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        //sql绑定操作
        }
}

databaseid是自己写的mysql,requireddatabaseid由于写在了mybatis.xml文件中,初始化时并不先加载里面内容,所以为空,造成不匹配,直接跳过绑定。

所以在执行sql时,就会找不到匹配的mapper

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
            String methodName = method.getName();
            Class<?> declaringClass = method.getDeclaringClass();
            MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
            if (ms == null) {
                if (method.getAnnotation(Flush.class) == null) {
                    throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
                }

                this.name = null;
                this.type = SqlCommandType.FLUSH;
            } else {
                this.name = ms.getId();
                this.type = ms.getSqlCommandType();
                if (this.type == SqlCommandType.UNKNOWN) {
                    throw new BindingException("Unknown execution method for: " + this.name);
                }
            }

        }
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
        if (validateIncompleteStatements) {
            this.buildAllStatements();
        }

        return (MappedStatement)this.mappedStatements.get(id);
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值