目录
1.准备工作
为了更好的演示MyBatis的初始化过程,先创建一个简单的java工程目录,如下所示:
1.1 创建实体类Product
package com.tongtong.pojo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
public class Product {
@Getter
@Setter
private String id;
@Getter
@Setter
private String name;
@Getter
@Setter
private String price;
}
1.2 ProductMapper.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="mybatis.ProductMapper">
<select id="selectProduct" resultType="com.tongtong.pojo.Product">
select * from Product where name = #{name}
</select>
</mapper>
1.3 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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/alipay?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis/ProductMapper.xml"/>
</mappers>
</configuration>
1.4 测试类MyBatisTest
package com.tongtong;
import com.tongtong.pojo.Product;
import lombok.extern.slf4j.Slf4j;
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;
@Slf4j
public class MyBatisTest {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Product product = sqlSession.selectOne("selectProduct","aa");
log.info("用户数据:{}",product);
}
}
2.MyBatis的初始化过程
2.1 读取配置文件,获取InputStream对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
2.2 创建SqlSessionFactoryBuilder对象,并调用build(InputStream inputStream)方法,获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
来具体看看在创建SqlSessionFactory对象时都做了什么工作
3.通过SqlSessionFactoryBuilder获取SqlSessionFactory
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
//创建XMLConfigBuilder对象,此对象是解析XML文件所用
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//parser.parse()是为了创建Configuration对象,Configuration对象的作用后续会介绍
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
;
}
}
return var5;
}
//上述var5 = this.build(parser.parse())调用的就是此方法,传递一个Configuration对象,可
以返回一个SqlSessionFactory对象,此处创建的是SqlSessionFactory的子类
DefaultSqlSessionFactory的实例对象
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
3.1 XMLConfigBuilder的解析
XMLConfigBuilder的体系结构:
-
XMLxxxBuilder
是用来解析XML配置文件的,不同类型XMLxxxBuilder
用来解析MyBatis配置文件的不同部位。比如:XMLConfigBuilder
用来解析MyBatis的配置文件,XMLMapperBuilder
用来解析MyBatis中的映射文件(如上文提到的ProductMapper.xml
),XMLStatementBuilder
用来解析映射文件中的SQL语句。 -
这些
XMLxxxBuilder
都有一个共同的父类——BaseBuilder
。这个父类维护了一个全局的Configuration
对象,MyBatis的配置文件解析后就以Configuration
对象的形式存储。 -
当创建XMLconfigBuilder对象时,会初始化Configuration,见下面代码:
-
在初始化的时候,一下别名会被注册到Configuration的typeAliasRegistry容器中。
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { //初始化Configuration super(new Configuration()); this.localReflectorFactory = new DefaultReflectorFactory(); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; } //下面代码是初始化Configuration的代码 public Configuration() { this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); this.typeAliasRegistry.registerAlias("MANAGED",ManagedTransactionFactory.class); this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class); this.typeAliasRegistry.registerAlias("LRU", LruCache.class); this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class); this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class); this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); this.languageRegistry.register(RawLanguageDriver.class); }
3.2 XMLConfigBuilder的解析过程
-
用DOM方法对Xml文件进行解析(这个知识点是关于xml解析的,有几种方式,此处使用的是dom解析)
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()),
environment, props);
}
进入XPathParser()中
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
this.commonConstructor(validation, variables, entityResolver);
this.document = this.createDocument(new InputSource(inputStream));
}
进入createDocument()中
private Document createDocument(InputSource inputSource) {
try {
//1.先获得一个DocumentBuilderFactory对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(this.validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
//2.获得一个DocumentBuilder对象,用于解析xml文件
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(this.entityResolver);
builder.setErrorHandler(new ErrorHandler() {
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
public void warning(SAXParseException exception) throws SAXException {
}
});
//3.解析
return builder.parse(inputSource);
} catch (Exception var4) {
throw new BuilderException("Error creating document instance. Cause: " + var4, var4);
}
}
上面的过程可以说是为dom解析做准备,而builder.parse(inputSource)中才是真正进行解析的地方:
public Document parse(InputSource is) throws SAXException, IOException {
if (is == null) {
throw new IllegalArgumentException(
DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
"jaxp-null-input-source", null));
}
if (fSchemaValidator != null) {
if (fSchemaValidationManager != null) {
fSchemaValidationManager.reset();
fUnparsedEntityHandler.reset();
}
resetSchemaValidator();
}
domParser.parse(is);
Document doc = domParser.getDocument();
domParser.dropDocumentReferences();
return doc;
}
有兴趣的话可以自己跟进去看看具体的解析步骤,对这一部分不熟悉的建议再好好看看关于xml解析这部分知识,这里不再赘述。
经过各种折腾,我们把xml配置文件顺利解析成了Document对象,保存在了XPathParser对象中。目前的情况是:XMLConfigBuilder中包含XPathParser,而XPathParser中包含Document(由xml配置文件经dom解析而来)。
现在回过头来分析上述的一段代码,如下
var5 = this.build(parser.parse());
parser即为我们获取的XMLConfigBuilder对象(里面包含Document),调用parse()方法,如下:
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
//调用parseConfiguration()方法解析配置文件
this.parseConfiguration(this.parser.evalNode("/configuration"));
//返回Configuration对象
return this.configuration;
}
}
this.parser.evalNode("/configuration")会返回一个XNode对象
下面就是XNode里面保存的数据,其实就是从xml中解析出的数据:
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/alipay?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis/ProductMapper.xml"/>
</mappers>
</configuration>
进入parseConfiguration方法中一探究竟:
从代码中可以看出对传过来的XNode对象中的各个属性进行解析
private void parseConfiguration(XNode root) {
//该方法的主要任务就是对XML文件中的属性进行解析
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
从上述代码中可以看出,XMLConfigBuilder会依次解析配置文件中的<properties>、<settings>、<environments>、<typeAliases>、<plugins>、<mappers>属性进行解析。
来简单的看几个属性的解析步骤:
3.2.1 <properties>节点的解析:
<properties>节点的配置信息:
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/alipay?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<properties>节点的解析过程:
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//获取<properties>节点的所有子节点>
Properties defaults = context.getChildrenAsProperties();
//获取<properties>节点的resource属性
String resource = context.getStringAttribute("resource");
//获取<properties>节点的url属性
String url = context.getStringAttribute("url");
//resource和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.");
}
//若只存在resource属性,那么获取resouce属性值对应的properties文件中的键值对,并
添加至default容器中
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
/反之,获取url属性值对应的properties文件中的键值对,并
添加至default容器中
defaults.putAll(Resources.getUrlAsProperties(url));
}
//获取Configuration中原本的属性,并添加至defaults容器中
Properties vars = this.configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
this.parser.setVariables(defaults);
//将default容器添加至configuration中
this.configuration.setVariables(defaults);
}
}
3.2.2 <mappers>节点的解析:
<mappers>节点的配置信息:
<mappers>
<mapper resource="mybatis/ProductMapper.xml"/>
</mappers>
<mappers>节点的解析过程:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
//遍历<mappers>下所有子节点
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
//如果当前节点为<package>
if ("package".equals(child.getName())) {
//获取<package>节点的name属性
resource = child.getStringAttribute("name");
//将该包下所有的MapperClass注册到configuration的mapperRegistry容器中
this.configuration.addMappers(resource);
//如果当前节点为<mapper>
} else {
//依次获取resource、url、class属性
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
//解析resource属性(mapper.xml文件的路径)
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
//将mapper.xml文件解析成输入流
inputStream = Resources.getResourceAsStream(resource);
//使用XMLMapperBuilder解析Mapper.xml,并将Mapper Class注册进
configuration对象中
mapperParser = new XMLMapperBuilder(inputStream, this.configuration,
resource, this.configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//解析url属性(mapper.xml文件的路径)
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream,
this.configuration, url,
this.configuration.getSqlFragments());
mapperParser.parse();
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify
a url, resource or class, but not more than one.");
}
//解析class属性(Mapper Class的全限定名)并将全限定名转换成Class对象
Class<?> mapperInterface = Resources.classForName(mapperClass);
//放入Configuration对象的mapperRegistry容器中
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
- MyBatis会遍历<mappers>下所有的子节点,如果当前子节点为<package>,则MyBatis会将该包下的所有的Mapper Class注册到Configuration的mapperRegistry容器中。
- 如果当前节点为<mapper>,则会依次获取resource、url、class属性,解析映射文件,并将映射文件对应的Mapper Class注册到Configuration的mapperRegistry容器中。
3.3 <Mapper>节点的解析过程
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
//<mapper>节点的解析
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource,
this.configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
//<mapper>节点的解析
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url,
this.configuration.getSqlFragments());
mapperParser.parse();
}
现在看下面这两段:
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource,
this.configuration.getSqlFragments());
mapperParser.parse();
在解析之前需要先创建XMLMapperBuilder对象:
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String
resource, Map<String, XNode> sqlFragments) {
//将configuration赋给BaseBuilder
super(configuration);
//创建MapperBuilderAssistant对象,从名字上可以看出它是MapperBuilder的协助者
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
- 首先会初始化父类BaseBuilder,并将configuration赋给BaseBuilder;
- 然后创建MapperBuilderAssistant对象,该对象为XMLMapperBuilder的协助者,用来协助XMLMapperBuilder完成一些解析映射文件的动作。
创建好了XMLMapperBuilder后,便可进入解析<mapper>的过程:
public void parse() {
//若当前的Mapper.xml没有被解析,则开始解析
if (!this.configuration.isResourceLoaded(this.resource)) {
//解析<mapper>节点
this.configurationElement(this.parser.evalNode("/mapper"));
//将该Mapper.xml添加至configuration的LoadResource容器中,下回无需再解析
this.configuration.addLoadedResource(this.resource);
//将该Mapper.xml对应的Mapper Class注册进configuration的mapperRegistry容器中
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
待续..........