MyBatis源码-配置原理篇(一)
- 先看个简单的配置:
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<!--<typeAliases>-->
<!--<typeAlias alias="employee" type="Employee"/>-->
<!--</typeAliases> -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${database.driver}"/>
<property name="url" value="${database.url}"/>
<property name="username" value="${database.username}"/>
<property name="password" value="${database.password}"/>
</dataSource>
</environment>
</environments>
<!--引入我们自己写的每一个接口的实现文件名称-->
<mappers>
<!--resource标识从类路径下找资源-->
<!--<package name="mapper"/>-->
<mapper resource="mapper/UserCardMapper.xml"/>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/UserAuthMapper.xml"/>
<mapper resource="mapper/PayOrderMapper.xml"/>
</mappers>
</configuration>
jdbc.properties
database.driver=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/yolyn_authpay?serverTimezone=UTC&characterEncoding=UTF-8
database.username=root
database.password=root
我们来看看配置是怎么解析的
在读取配置的时候我们会写下下面几行代码:
public class SqlSessionUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
build方法点进去
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
再进build方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
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.
}
}
这里可以看到创建了一个XMLConfigBuilder来解析这个文件流,然后传到build方法,先看下这个build方法再进parse方法看看:
build方法
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
注意看下这个build方法传入的是啥,一个Configuration实例,那说明XMLConfigBuilder.parse()方法返回的是一个Configuration,看下parse方法
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
看返回的是啥,啊哈,一个Configuration,并且这里用了一个parsed变量保证配置文件只解析一次,parsed初始值为false,看下这个parseConfiguration方法
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));//获取额外的properties配置
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
激不激动,刺不刺激,这里可干了好多事情哦,我们挑几个看下:
-
- propertiesElement(root.evalNode(“properties”));
获取并解析额外的配置文件,我们在这个例子一开始写了一个jdbc连接配置,我们看下这个方法:
- propertiesElement(root.evalNode(“properties”));
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");//不能同时存在两个配置源
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
我们要知道这个Properties是一个HashTable,解析后的配置以键值对的形式保存在这个HashTable里面,不信的话可以看下getResourceAsProperties方法
getResourceAsProperties方法
public static Properties getResourceAsProperties(String resource) throws IOException {
Properties props = new Properties();
InputStream in = getResourceAsStream(resource);
props.load(in);
in.close();
return props;
}
public synchronized void load(InputStream inStream) throws IOException {
load0(new LineReader(inStream));
}
private void load0 (LineReader lr) throws IOException {
//此处省略好多代码
String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
put(key, value);
}
}
看到了吧,还有啥好说的,就是这样,到这里一定好奇一开始配置连接池的时候,额外配置文件jdbc.properties的值是怎么塞进去的吧,我们再看下
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${database.driver}"/>
<property name="url" value="${database.url}"/>
<property name="username" value="${database.username}"/>
<property name="password" value="${database.password}"/>
</dataSource>
</environment>
</environments>
哎呀,自己看去吧,懒得扒了。。。
反正这些配置相关的东西大多最终都是放在Configuration里面,包括Mapper,不信你自己看去,除此之外还有些东西是放在map里面,如TypeHandler啦,不信你可以去看XMLConfigBuilder的父类BaseBuilder中的TypeHandlerRegistry