进行源码剖析,首先要先了解 Mybatis 的执行过程(或者说原理),其实就是如图的四个步骤:
根据思路写代码,再次查看代码逻辑发现:
创建 SqlSessionFactory
其实就是根据逻辑一行行写的代码,接下来关注源码:按住 ctrl 点进去 build 方法:
返回另一个 build 方法,再次点击
先不关注怎么解析 xml 的,首先看如果创建工厂,其实就是 传递解析 xml 的 config,然后 new 了一个默认的工厂,直接返回,此时工厂建立完毕
接下来过一下如何解析 xml 文件:
点击 parse() 方法,进入
再次深入
然后发现这个代码,很长,不过一行行看能够大概知道什么意思,打个断点测试一下:
进入 else 里面,点击 new XMLMapperBuilder 方法进入内部
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");
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);
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);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
最终发现,解析 xml 的实质就是将什么 返回值,入参,sql 等创建成 statement
此时完成 SqlSessionFactory 的创建,得到一个与 xml 文件有关的 DefaultSqlSessionFactory,然后开始创建 SqlSession
创建 SqlSession
然后创建 SqlSession,通过打断点测试,进入方法内部,发现其实 DefaultSqlSessionFactory 是 SqlSessionFactory 的实现类
再次进入 openSessionFromDataSource 方法
创建 executor 然后通过配置,创建一个对应的 DefaultSqlSession,也是一个 SqlSession 的实现类
获取 Mapper 对象
继续打断点测试,查看 mapper 层是如何获取的,一直进入方法内部,可以发现,又有一个 Mapper 的工厂
根据断点一路深究,
很明显看出,是一个动态代理,通过 工厂,创建代理对象,执行 invoke 方法,完成 mapper 对象的创建
根据创建 DefaultSqlSessionFactory 得到的信息,创建出一个 mapper 对象
根据 Mapper 对象,得到返回结果
然后看执行 mapper 的方法,如何运行 sql 优势怎么得到结果的,进入方法内部,如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
执行返回多个结果的方法:
发现,其实就是根据之前得到的 statement 对象,获取当前标签的唯一标识符,然后开始执行执行里面的 sql
这里给实体类赋值,是否可赋值,就是得出的结果是否与 resultType 相符合,(如果实体类,判断是否相对应
然后执行完结果,此时完成一个查找的全部流程