文章目录
mybatis源码分析-配置文件解析(1)
配置⽂件解析过程分析
第一步要做的事情一般是根据配置文件构建 SqlSessionFactory 对象。相关代码大致如下:
InputStream inputStream = Resources.getResourceAsStream("MyBatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
我们首先会使用 MyBatis 提供的工具类 Resources 加载配置文件,得到 一个输入流。然后再通过 SqlSessionFactoryBuilder 对象的 build 方法构建 SqlSessionFactory 对象。这里的 build 方法是我们分析配置文件解析过程的入口方法。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//inputStream xml输入流
// environment为Null ,properties为null,
//配置文件的加载以及解析全部委托给了XPathParser。最终使用的是JDK的xml解析器,而不是在这里使用第三方的dom4j等,底层还是使用的xpath的当时来进行的节点解析。
//这边我们不去了解怎么解析xml里面的节点的
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.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
parser.parse()解析xml配置
build(parser.parse());这个方法就是把parser.parse()继续出来的结果当成mybatis的上下文Configuration放到DefaultSqlSessionFactory的工厂里面。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//解析/configuration,就是我们的mybatis-config.xml里面的configuration的节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//解析节点,我们这边就看几个比较关键的解析,其他可以看完主流程再去看
private void parseConfiguration(XNode root) {
try {
// <properties>
// <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
// <property name="username" value="root"/>
// <property name="password" value="123456"/>
// </properties>
//解析properties
//会放在 variables中
//解析properties节点
propertiesElement(root.evalNode("properties"));
/* <settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="useGeneratedKeys" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>*/
/*"mapUnderscoreToCamelCase" -> "true"
"useGeneratedKeys" -> "true"
"lazyLoadingEnabled" -> "true"
"cacheEnabled" -> "true"*/
// 解析 settings 配置,并将其转换为 Properties 对象
Properties settings = settingsAsProperties(root.evalNode("settings"));
//这个方法判断settins里面有咩有vfsImpl,这边先不看
loadCustomVfs(settings);
//解析typeAliases节点
// <typeAliases>
//<typeAlias alias="User" type="com.wjk.entity.User"/>
//</typeAliases>
//类是typeAliasRegistry 中的TypeAliasRegistry中别名的注册
typeAliasesElement(root.evalNode("typeAliases"));
// 解析 plugins 配置
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// settings 中的信息设置到 Configuration 对象中
settingsElement(settings);
// 解析 environments 配置
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析 typeHandlers 配置
typeHandlerElement(root.evalNode("typeHandlers"));
//<mappers>
//<mapper resource="UserMapper.xml"/>
//</mappers>
// <mappers>
//<package name="com.wjk.entity"/>
//</mappers>
// 解析 mappers 配置
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
解析配置文件,这边先看比较关键的配置解析
1.解析properties
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//Properties的chird节点
Properties defaults = context.getChildrenAsProperties();
//password -> 123456
//url -> jdbc:mysql://localhost:3306/mybatis
//username -> root
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);
//password -> 123456
//url -> jdbc:mysql://localhost:3306/mybatis
//username -> root
//设置到Properties variables里面
configuration.setVariables(defaults);
}
}
2.解析setting节点
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
//mapUnderscoreToCamelCase -> true 大小写转换
// "useGeneratedKeys" -> "true" 自增主键
// "lazyLoadingEnabled" -> "true" 懒加载
// "cacheEnabled" -> "true" 二级缓存打开
Properties props = context.getChildrenAsProperties();
//metaClass是啥就是我们的元数据类,里面存储的是比如类的成员变量,getter/setter 方法等,这个解析有点复杂,我们就直接认为我们可以从这个里面获得我们的构造方法,getter,setter方法就行
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
//如果没有抛出异常
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
3.解析environments节点
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
//判断当前的environment是不是一样的
if (isSpecifiedEnvironment(id)) {
//事务解析<transactionManager type="JDBC"/>
//Jdbc事务工厂
//事务类型 JDBC 这个配置直接简单使用了 JDBC 的提交和回滚设置。 它依赖于从数据源得 到的连接来管理事务范围。
//MANAGED从来不回滚或提交一个连接而它会让 容器来管理事务的整个生命周期(比如 Spring 或 JEE 应用服务器的上下文) 默认 情况下它会关闭连接。
// 然而一些容器并不希望这样, 因此如果你需要从连接中停止 它,将 closeConnection 属性设置为 false。
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//设置了事务工厂,和数据源
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
4.解析typeHandlers的节点
在 MyBatis 中,数据库类型和 Java 类型之间的转换任务是委托给类型处理器 TypeHandler 去处理的,MyBatis 提供了一些常见类型的类型处理器,除此之外,我们还可以 自定义类型处理器以非常见类型转换的需求,TypeHandlerRegistry初始化的时候,就会初始化一些基础的映射
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
//注册进去
typeHandlerRegistry.register(typeHandlerPackage);
} else {
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
解析mapper节点里面的内容比较多,我们下个文章见