一、导读与猜想
在开始分析Mybatis的源码之前,我们不妨来猜想一下,Mybatis是如设计的?
使用过Mybatis框架都知道,使用Mybatis的过程大致经历如下步骤:
- 创建一张表t_people
- 创建一个实体People
- 创建PeopleMapper接口
- 创建PeopleMapper.xml文件
- 创建mybatis-config.xml配置文件,里面配置数据库连接信息(dbUrl、user、password等),mappers、mapper标签等;也可以不使用xml,使用yml配置数据源+注解方式配置数据源+mapper。
猜想Mybatis的设计与使用流程:
1. 读取并装载mybatis-config.xml,输入流InputStream。
2 .解析输入流并把mybatis-config.xml配置文件中相关配置项解析,校验,保存起来。
3.创建sqlSessionFactory对象,在我们的印象里,session就是一次会话,所以我们可以理解sqlSessionFactory就是个工厂类,就专门创建sqlSession对象,并且这个sqlSessionFactory工厂类是唯一不变的(单例)。
4.创建sqlSession,SqlSession中保存了配置文件内容信息和执行数据库相关的操作。
5.获取PeopleMapper对象,但是PeopleMapper是接口,并且没有实现类。怎么就可以调用其方法呢?这里猜想可能用到了动态代理。
6.PeopleMapper接口中的方法是如何关联到SQL的,这个猜想可能是有个专门映射的类,另外,肯定使用到了接口全路径名+方法名称,这个才能确保方法和SQL关联(主要是使用的时候,都是方法名必须和SQL中statementId一致,由此猜想的)。
7.最后底层使用JDBC去操作数据库。
8.作为一个持久化框架,很有可能会使用到缓存,用来存储每次查询数据。
// 我们平时使用的大致流程如下,当然使用springboot就不是这样,不过大同小异。
//读取mybatis-config.xml
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//解析mybatis-config.xml配置文件,创建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//创建sqlSession
sqlSession = sqlSessionFactory.openSession();
//创建PeopleMapper对象(PeopleMapper并没有实现类)
PerpleMapper peopleMapper= sqlSession.getMapper(PeopleMapper.class);
//调用PeopleMapper对象的方法
People people = peopleMapper.selectById(1);
第二个猜想,Mybatis是如果设计,并把配置信息保存到Configuration中的?
第一步是读取mybatis-config.xml配置文件,获得输入流InputStream。
// 我们先从build()方法开始
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactoryBuilder中有好几个build方法,这在java中叫方法重载,如下图:
从上图可以看到SqlSessionFactory中提供了三种读取配置信息的方法后:字节流、字符流和Configuration配置类。
build方法里面主要创建XMLConfigBuilder对象,这个类是BaseBuilder的子类,BaseBuilder类图。
看到这些子类基本上都是以Builder结尾,所以这里使用的是建造者设计模式。
这个类名可以猜出给类就是解析xml配置文件的。然后我们继续进入
从上图看到new XPathParser(...),这个类位于org.apache.ibatis.parsing包下,主要用于解析Mybatis中的mybatis-config.xml、xxxMapper.xml等xml文件。
继续分析源码
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;
}
构造一个XMLConfigBuilder对象,给属性设置相应值。
然后我们再回到SqlSessionFactoryBuilder中的build方法里:
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
build(parser.parse());
对应的parse()方法源码如下:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//mybatis-config.xml的一级标签
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
再继续看parseConfiguration()方法
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
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);
// 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);
}
}
再结合 mybatis-config.xml配置文件和解析方法,如果回头去看mybatis-config.xml中的所有标签,包括一级标签、二级标签、三级标签等。parseConfiguration()方法是解析xml中的标签,并将标签内容封装在Configuration对象中。
如果我们想知道有哪些标签可以定义,可以看org.apache.ibatis.builder.xml下的mybatis-3-config.dtd,这里已经定义了
与之对应的具体标签定义可查看mybatis-config.xsd,如下:
我们平时用得最多的是xxxMapper.xml文件,所以更关心这个xml文件里面有哪些标签,这个在mybatis-3-mapper.dtd也定义了,与之对应的mybatis-mapper.xsd可查看到具体的标签,如下:
还有很多select、update、delete等我们平时常用的标签,都在mybatis-mapper.xsd中可查看到。
挑些重点:我们来看看这些标签内容是如何存入configuration对象中?
我们主要查看org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration中的几个重要方法:
- this.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 = this.configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
this.parser.setVariables(defaults);
this.configuration.setVariables(defaults);
}
}
- this.typeAliasesElement(root.evalNode("typeAliases"));
private void typeAliasesElement(XNode parent) {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String alias;
if ("package".equals(child.getName())) {
alias = child.getStringAttribute("name");
this.configuration.getTypeAliasRegistry().registerAliases(alias);
} else {
alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class> clazz = Resources.classForName(type);
if (alias == null) {
this.typeAliasRegistry.registerAlias(clazz);
} else {
this.typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException var7) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + var7, var7);
}
}
}
}
}
- this.pluginElement(root.evalNode("plugins"));
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
//获取interceptor标签
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
this.configuration.addInterceptor(interceptorInstance);
}
}
}
Configuration中interceptorChain用来存储所有定义的插件。
public void addInterceptor(Interceptor interceptor) {
// 将插件存入interceptorChain中
this.interceptorChain.addInterceptor(interceptor);
}
InterceptorChain插件链(连接链),责任链模式。
public class InterceptorChain {
private final List interceptors = new ArrayList();
public InterceptorChain() {
}
public Object pluginAll(Object target) {
Interceptor interceptor;
for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
interceptor = (Interceptor)var2.next();
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
this.interceptors.add(interceptor);
}
}
- 最后看看mapper是怎样解析放到configuration中的,主要的解析方法是在org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement方法中
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
//自动扫描包下所有映射器
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
//放到配置对象configuration中
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
//根据文件存放目录,读取XxxMapper.xml
inputStream = Resources.getResourceAsStream(resource);
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);
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> mapperInterface = Resources.classForName(mapperClass);
// 重点在这里,将xxxMapper接口放入configuration中
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
至此,配置文件mybatis-config.xml和我们定义映射文件XxxMapper.xml就全部解析完成。
总结: 从上面的代码和流程可以看出,关于其他配置项,解析方式类似,最终都保存到了一个Configuration大对象中。Configuration对象类似于单例模式,就是整个Mybatis中只有一个Configuration对象。