在项目上遇到如下情况,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);
}