博学谷学习记录 自我总结 用心分享 | MyBatis源码刨析

发布时间 2023-10-13 14:43:09作者: 刚刚一个

  Mybatis底层源码分析
1.概要介绍
MyBatis 是一款优秀的持久层框架,也是当前最流行的java持久层框架之一,它内部封装了jdbc,使开发
者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。采
用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用
与jdbc api打交道,就可以完成对数据库的持久化操作。
Mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中SQL
的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
2.总体执行流程
通过getResourceAsReader(String resource)方法读取Resources下的mybatis.xml配置文件转换成Reader
通过构建者的设计模式去生成SqlSessionFactory对象:new SqlSessionFactoryBuilder().build()方法去生成
有了SqlSessionFactory的工厂对象后,就可以通过openSession()去获取DefaultSqlSession对象
然后就可以对数据库进行insert、update、delete、select操作
进行了数据库的相关操作之后,通过sqlSession.commit()进行事务的提交
最后通过sqlSession.close()关闭连接,释放资源
代码如下:

3. 对各个流程的细分详解

3.1 读取配置文件转换成Reader
public static Reader getResourceAsReader(String resource) throws IOException {
InputStreamReader reader;
if (charset == null) {
reader = new InputStreamReader(getResourceAsStream(resource));
} else {
reader = new InputStreamReader(getResourceAsStream(resource), charset);
}
return reader;
}
3.2 使用build()方法生成SQLSessionFactory对象的流程
public SqlSessionFactory build(Reader reader) {
return this.build((Reader)reader, (String)null, (Properties)null);
}
看上去是用的build(Reader reader)方法来构建的对象,但是内部实际上调用的是build(Reader reader,String environment,Properties properties)方法,然后声明了一个XML配置器parser,返回了一个builde(parser.parse());

public SqlSessionFactory build(Reader reader, String environment, Properties
properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment,
properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
……;} finally {……}
return var5;
}
3.2.1 分析build(parser.parse())
这个方法是用来分析配置文件是否被解析过

public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
//这个地方表示要解析configuration中所有内容
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
3.2.2 分析parseConfiguration(…)方法
private void parseConfiguration(XNode root) {
try {
//解析配置文件中的properties节点
this.propertiesElement(root.evalNode("properties"));
//解析settings节点里的所有内容
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
/*
* 这两个方法用于远程加载settings
*/
this.loadCustomVfs(settings);
this.loadCustomLogImpl(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"));
//这里是将所有的properties设置到Configuration中并加载一些默认配置
this.settingsElement(settings);
//解析environments的节点
this.environmentsElement(root.evalNode("environments"));
//此处是多数据源的配置 但一般不配置
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//这里是用来处理Mybatis的数据类型和java的数据类型的映射关系的 一般也不配置
this.typeHandlerElement(root.evalNode("typeHandlers"));
//这里最重要! 用来获取mappers节点的内容
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration.
Cause: " + var3, var3);
}
}
3.2.2.1 解析propertiesElement()的过程
private void propertiesElement(XNode context) throws
Exception {
if (context != null) {
//获取properties内容下的所有值
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
//判断resource和url的值,两者不能同时存在
if (resource != null && url != null) {
……
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
//获取configuration的值
Properties vars = this.configuration.getVariables();
if (vars != null) {
//将configuration中的properties和vars中properties的属性融合到一起
defaults.putAll(vars);
}
this.parser.setVariables(defaults);
//将所有解析到的properties放到configuration中
this.configuration.setVariables(defaults);
}
}
3.2.2.2 解析取别名的过程typeAliasesElement()
最终的结论:是将取好的别名和class放到了configuration中的TypeAliasRegistry这个对象中的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");
//实际调用的方法是registerAliases(String packageName, Class<?> superType)
//获取所有需要取别名的类
this.configuration.getTypeAliasRegistry().registerAliases(alias);
} else {
alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
//判断是否有Alias注解
this.typeAliasRegistry.registerAlias(clazz);
} else {
//注册别名,并且此方法可以看到为什么可以区分大小写
this.typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException var7) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + var7, var7);
}
}}}}
3.2.2.3 解析插件的过程pluginElement()
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这个接口的实现类的对象,并设置属性
Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).
getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
//将interceptor对象设置到configuration中去
this.configuration.addInterceptor(interceptorInstance);
}
}
}
3.2.2.4 解析environmentsElement()
获取要使用的数据库环境,可以是MySQL、Oracle等,通过比较environments和比较environment的id来判断使用哪种数据库。同时,这里还判断了transactionManager这个节点,通过dataSource获取了数据源,最后采用构建者的设计模式将environment对象放到configuration中。

private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (this.environment == null) {
this.environment = context.getStringAttribute("default");
}
Iterator var2 = context.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String id = child.getStringAttribute("id");
//比较environment的id来判断使用哪种数据库
if (this.isSpecifiedEnvironment(id)) {
//这里是判断transactionManager这个节点的
TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
//通过dataSource来获取数据源
DataSource dataSource = dsFactory.getDataSource();
Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
//使用构建者的设计模式来把environment对象放到configuration中
this.configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
3.2.2.5 解析mapperElement()
这里通过判断配置方式,第一种就是通过包的路径进行配置,第二种就是通过resource中的resource、url、class(三者属性之一)进行配置。

假设配置的是resource属性,首先通过路径去获取到resource的流,然后通过构建者的设计模式初始化一个专门解析Mapper.xml的解析器,最后通过mapperParser.parse()来解析整个mapper.xml文件
假设配置的是url属性,首先同样是将url转化为流对象,然后通过构建者的设计模式去初始化一个专门解析Mapper.xml的解析器,最后通过mapperParser.parse()来解析整个mapper.xml文件
假设配置的是mapperClass属性,首先通过类名去获取到class对象,最后直接将这个class对象配置到configuration中去
这里需要进一步解析mapperParser.parse()方法。见下文
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//这个条件应该成立 按照我们写的规范
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
//获取Mapper.xml的流 字节流
InputStream inputStream = Resources.getResourceAsStream(resource);
//初始化一个专门解析Mapper.xml的解析器
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//解析整个mapper.xml文件
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
3.2.2.5.1 mapperParser.parse()方法探讨
首先判断configuration是否已经加载过了,如果加载过了就执行parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();这三个方法,如果没有加载过,则先解析mapper节点,然后将resource配置到configuration中去,然后通过bindMapperForNamespace()方法将mapper接口和命名空间绑定到一起。

mapper节点的解析底层:
解析这个节点时,首先拿到命名空间,然后将其放进builderAssistant对象中去,这里相当于是设置了一个全局的名称,后面这些内容解析的时候会用到这个名称,接着解析cache-ref和cache、parameterMap、resultMap和Sql片段,最后是解析增删改查的地方,通过上下文构建statement对象,真正解析增删改查的方法是buildStatementFromContext(list, null),通过构建者的设计模式生成了一个statementParser对象专门用来解析增删改查标签,parseStatementNode()通过增删改查各个标签的属性进行解析与设置,最终通过构造者模式添加到builderAssistant对象中,这是用来构建statement对象的,最重要的是addMappedStatement()这个方法,首先判断是不是select的方法,因为select方法涉及到缓存,判断完了之后,通过构建者模式构建statement对象,并把它添加到MappedStatement类型的statement变量中,最终将statement对象放到configuration这个对象中,就是将所有的增删改查的标签都解析成了一个statement对象,放到了configuration这个对象中)

bindMapperForNamespace()方法的底层解析:
首先通过builderAssistant.getCurrentNamespace()获取到命名空间(由于是接口的实现,所以这里的命名空间可以是包路径,用于后面反射获取当前接口),然后获取命名空间的class对象,然后将这个Mapper接口的class对象添加进configuration中。

/**
* 解析Mapper.xml文件的位置
*/
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//解析mapper节点下的所有东西
configurationElement(parser.evalNode("/mapper"));

//这里将resource配置到configuration
configuration.addLoadedResource(resource);

//下面这句话的意思很重要就是 我们绑定Mapper接口和命名空间之间这个纽带
bindMapperForNamespace();
}

parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
/**
* 解析增删改查的节点
* @param context
*/
private void configurationElement(XNode context) {
try {
//首先拿到命名空间
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//将命名空间放到builderAssistant这个对象中去
builderAssistant.setCurrentNamespace(namespace);

cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));

//这句话才是解析增删改查的地方1
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
/**
* 解析增删改查的地方2
* @param list
* @param requiredDatabaseId
*/
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {

//初始化了一个解析 增删改查标签的这个解析器
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//这里就是解析数据的地方2
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}

/**
* 这里才是真正的解析增删改查的地方
*/
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");

if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}

String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());

String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);

String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);

// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);

// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");

//这里才是主角
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

/**
* 将mapper和命名空间绑定到一起的地方
*/
private void bindMapperForNamespace() {
//获取咋们的命名空间
String namespace = builderAssistant.getCurrentNamespace();

if (namespace != null) {
Class<?> boundType = null;
try {
//这句话是啥意思? Class.forName("")
//获取命名空间的class对象
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//如果上面出现了异常 说明我们的命名空间是随便写的一个值
//如果是随便写的 那么这里就不执行
//ignore, bound type is not required
}

//说明我们的命名空间设置的是 Mapper接口的全路径 随意这里就需要进行处理
if (boundType != null) {

//下面判断这个Configuration中是否已经绑定了这个Mapper对象
//如果是没有绑定过这个对象
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);

//添加我们的Mapper对象
// boundType :这个是Mapper接口的class对象
configuration.addMapper(boundType);
}
}
}
}
3.3 使用getSqlSession()获取到sqlSession对象
3.4 通过sqlSession对象的getMapper(UserMapper.class) 这里使用User来举例
3.4.1 getMapper(UserMapper.class) 方法分析:
configuration.getMapper(type, this)根据传入的接口class对象来获取mapper,上面的方法是由mapperRegistry.getMapper(type, sqlSession)返回的mapper代理类对象来的。

如何生成的代理类对象?
首先knownMappers.get(type)根据对应的class类型,来找对应的MapperProxyFactory,然后通过mapperProxyFactory.new instance(sqlSession)来返回接口的实现代理类对象。

/**
1. 获取Mapper接口的对象 ?
2. @param type
3. @param sqlSession
4. @param <T>
5. @return
*/
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {

//获取MapperProxyFactory对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
//如果没有获取到 那么说明 这个对象是有问题的
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//最终返回我们的接口的对象的地方在这里
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

3.5 通过sqlSession.insert(“UserMapper.addUser”,user)开始数据库操作(这里举例insert)delete update select以此类推
sqlSession.insert(“UserMapper.addUser”,user)源码分析:
sqlSession.insert(“UserMapper.addUser”,user)最终是调用的update(String statement, Object parameter)方法,执行sql,statement:namespace.id,parameter:执行sql所需要的参数

/**
* 执行到了这里
* @param statement Unique identifier matching the statement to execute.
* @param parameter A parameter object to pass to the statement.
* @return
*/
@Override
public int insert(String statement, Object parameter) {
//最终调用的是 update方法
return update(statement, parameter);
}
⬇️ 先设置dirty,然后通过configuration.getMappedStatement()获取所有增删改查标签封装的mapperedStatement对象,最后通过执行器去执行executor.update方法

@Override
public int update(String statement, Object parameter) {
try {
dirty = true;

// statement:命名空间.id
// MappedStatement:解析Mapper.xml文件之后 将增删改差的每一组标签弄成了MappedStatement对象
//最终以 命名空间.id为key MappedStatement为值放到了 Configuration中的
//拿出来的目的是干嘛? 找到SQL语句 找到参数.... 执行SQL语句
MappedStatement ms = configuration.getMappedStatement(statement);

//执行代码
// 返回的Executor是 SimpleExecutor
// wrapCollection(parameter) //就是将你传输的参数 弄成SQL中的集合 处理请求的参数
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {……} finally {……}
}
⬇️ 这里调用doUpdate方法,先获取configuration这个对象,然后通过configuration.newStatementHandler获取处理器,再获取prepareStatement对象
(获取的prepareStatement对象是在SimpleExecutor类中的prepareStatement调用父类BaseExecutor来生成的),最后通过handler.update(stmt)去执行sql语句

@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}

/**
* 所以最终执行到这里来了
* @param ms 这个里面包含了SQL语句
* @param parameter:这个里面是SQL语句执行需要的参数
* @return
* @throws SQLException
*/
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {

//JDBC中的那个东西
Statement stmt = null;

try {
//从statement中获取到整个解析的后存放数据的类
Configuration configuration = ms.getConfiguration();

//handler:xxx处理器
// statement:这里玩的是 statement的处理器
// this:SimpleExecutor
// ms :MappedStatement
// parameter:SQL需要的数据

//返回的对象 RoutingStatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);


//获取到了 Statement对象
// stmt :statement? PrepareStatment? CallableStatement ?
// 这里返回的是PrepareStatment对象
stmt = prepareStatement(handler, ms.getStatementLog());

//执行SQL语句
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}