问题
现象: 在测试$和#区别时, mapper.xml使用${username}, 发现始终都被赋值root(即连接用户名)
原因: 在xml配置文件中, ${xxx}占位符, 会被已经存在的键值对优先解析, 如jdbc.properties
解决: Xxxmapper.xml中使用${xxx}时, 避免和其他键值对重名
UserMapper.xml配置
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO user(username, password) VALUES(${
username}, #{
password})
</insert>
开启剖析之路
类比似先查看mybatis-config.xml配置的${driver}, ${url}, ${username}, ${password}如何被解析?
查看源码:
// 输入流
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// sqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
查看build方法, 一层层点进去, 进到下面这个build, 核心方法是parser.parse方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 核心在于parser.parse()
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
查看parser.parse方法
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 核心, 解析configuration配置信息
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
查看parseConfiguration方法, 关注三个方法
propertiesElement(root.evalNode(“properties”));
environmentsElement(root.evalNode(“environments”));
mapperElement(root.evalNode(“mappers”));
private void parseConfiguration(XNode root) {
try {
// properties标签解析, 把properties文件里面的键值对存到variables
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// 解析environments, 这里面带有${driver}, ${url}, ${username}, ${password}
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析mappers标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
查看environmentsElement方法
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren(