1 概述
所有框架的初始化其实就是读取配置文件中的数据来生成配置对应的类的对象。针对Mybatis框架的初始化其实就是读取配置文件和Mapper配置文件来生成Condiguration类的对象。下面我们来看一下具体的初始化过程。
2 利用XML初始化
2.1 应用示例
首先我们来看一个使用Mybatis的简单示例。示例工程结构如下:
针对上面的示例,关键源码如下:
(1)datasource.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/liutao_dev?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123
(2)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="datasource.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- class 级别的指定 -->
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
(3)UserMapper.xml
<?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="com.liutao.mybatis.mapper.UserMapper">
<select id="findUserInfo" resultType="com.liutao.vo.User" parameterType="java.lang.String">
select name, age,password from user where name = #{username};
</select>
</mapper>
(4)UserMapper.java
package com.liutao.mybatis.mapper;
import com.liutao.vo.User;
/**
* 用户Mapper持久层
*
* @author LIUTAO
* @version 2017/5/23
* @see
* @since
*/
public interface UserMapper {
/**
* 根据用户名查询用户
* @param username
* @return
*/
User findUserInfo(String username);
}
(5)测试类test.java
import com.liutao.mybatis.mapper.UserMapper;
import com.liutao.vo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
* @author: LIUTAO
* @Date: Created in 2018/10/15 14:44
* @Modified By:
*/
public class test {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = sqlSessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.findUserInfo("liubei");
System.out.println("user=" + user);
}
}
运行测试代码,我们可以看见输出结果为:
user=User{name='liubei', age=12, password='123', id=0}。
上面就是针对简单示例的源码的展示,我们这里要分析mybatis的初始化,也就是分析Configuration对象的创建过程,那么这个对象又是在哪里创建的呢?接下来我们来看一下具体的方法调用流程。
2.2 初始化方法调用流程
查看使用demo,首先调用SqlSessionFactoryBuilder的默认构造器生成SqlSessionFactoryBuilder对象。在这里其实使用了建造者设计模式来生成SqlSessionFactory。接着调用build方法来生成SqlSessionFactory。我们来看一下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.
}
}
}
在此我们需要注意调用此方法传入的environment和properties都为null。
我们来看一下build方法内部做了什么。
首先生成XMLConfigBuilder对象。
查看XMLConfigBuilder的源码,我们可以看出它继承自BaseBuilder。
public class XMLConfigBuilder extends BaseBuilder {
... ...
看一下BaseBuilder的源码
public abstract class BaseBuilder {
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
... ...
我们发现,BaseBuilder持有Configuration属性,这个属性也就是通过配置文件转换成的对象。BaseBuilder不仅仅有XMLConfigBuilder一个子类,而且拥有以下子类:
这里调用了XMLConfigBuilder的构造器来生成XMLConfigBuilder。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
这里首先要调用XPathParser的构造器来生成一个XPathParser,然后再调用如下构造器。
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
在这里,我们先来看一下new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())做了些什么。
XPathParser是用来解析XML文件的,包括检测XML文件格式。
我们来看以下XPathParser拥有的属性:
public class XPathParser {
private Document document;// 用来解析xml文件
private boolean validation;//验证
private EntityResolver entityResolver;//通过key查找dtd文件
private Properties variables;
private XPath xpath;//将元素转换成为节点信息
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
在分析这个构造器之前,我们先来看一下传入的new XMLMapperEntityResolver()是个什么东西。
XMLMapperEntityResolver的作用其实就是找到对应的dtd文件。我们mybatis的配置文件,不管是属性配置文件或者是mapper配置文件都会发现有类似下面的内容:
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
其实每个和mybatis相关的配置文件都有内容,也就是有一个PUBLICID和SYSTEMID。
查看XMLConfigBuilder的源码:
public class XMLMapperEntityResolver implements EntityResolver {
private static final Map<String, String> doctypeMap = new HashMap<String, String>();
private static final String IBATIS_CONFIG_PUBLIC = "-//ibatis.apache.org//DTD Config 3.0//EN".toUpperCase(Locale.ENGLISH);
private static final String IBATIS_CONFIG_SYSTEM = "http://ibatis.apache.org/dtd/ibatis-3-config.dtd".toUpperCase(Locale.ENGLISH);
private static final String IBATIS_MAPPER_PUBLIC = "-//ibatis.apache.org//DTD Mapper 3.0//EN".toUpperCase(Locale.ENGLISH);
private static final String IBATIS_MAPPER_SYSTEM = "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_CONFIG_PUBLIC = "-//mybatis.org//DTD Config 3.0//EN".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_CONFIG_SYSTEM = "http://mybatis.org/dtd/mybatis-3-config.dtd".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_MAPPER_PUBLIC = "-//mybatis.org//DTD Mapper 3.0//EN".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_MAPPER_SYSTEM = "http://mybatis.org/dtd/mybatis-3-mapper.dtd".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
可以发现这里定义了大量的PUBLICID和SYSTEMID。
至于具体的使用,我们后面再谈。
现在我们有回过头来看一下XPathParser构造器内部的实现情况。
首先调用了commonConstructor函数。
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
这个函数做的其实就是给XPathParser对象进行赋值。
最后调用createDocument函数来生成document。
完成了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;
}
进入evalNode函数。
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
最终调用的是下面的函数:
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
这个函数的作用其实就是将document中的Configuration节点给解析成XNode对象。
XNode其实就是对XML中的节点进行的封装。
public class XNode {
private Node node;
private String name;
private String body;
private Properties attributes;
private Properties variables;
private XPathParser xpathParser;
... ...
最终parseConfiguration函数利用解析成的XNode对象,来获取里面的各种属性并且设置到Configuration对象上。
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(root.evalNode("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);
}
}
最终完成了由XML配置文件的数据到Configuration的转换。
现在我们回到SqlSessionFactoryBuilder的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.
}
}
}
最终调用build(Configuration configuration)方法来生成SqlSessionFactory对象。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
至此,mybatis的初始化过程分析完成,我们将在后面的文章中分析具体是如何解析xml配置文件和mapper文件的。