一文看懂 mybatis 底层运行原理解析
public interface DataDao {
public int mysqlInsert(Map<String, Object> map) throws Exception;
}
接口对应的?DataDao.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="dao.DataDao">
<insert id="mysqlInsert" parameterType="map" >
insert into ${tableName}
(${columns})
values
(${colValues})
</insert>
</mapper>
首先我们会从 mybatis 的入口加载的地方来进行解读,先看一段 mybatis 的插入代码:
/**
传入 map 参数,执行插入
@param map
@return
*/
public static int mysqlInsert(Map<String, Object> map) throws Exception {
SqlSession session = null;
int index=0;
try {
session = SessionFactory.getSession();
DataDao dataDao = session.getMapper(DataDao.class);
index=dataDao.mysqlInsert(map);
if(index>0){
logger.info("insert success: "+index);
}
session.commit(true);
} catch (Exception e) {
logger.error("插入异常:", e);
session.rollback(true);
throw e;
} finally {
session.close();
}
return index;
}
public class SessionFactory {
private static Logger logger = LoggerFactory.getLogger(SessionFactory.class);
private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
private static SqlSessionFactory sqlSessionFactory;
//初始化 mybatis
static {
String resource = "mybatis-config.xml";
Reader reader = null;
try {
reader = Resources.getResourceAsReader(resource);
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
} catch (IOException e) {
logger.error("加载配置文件错误:",e);
e.printStackTrace();
}catch (Exception e){
logger.error("加载配置文件错误:",e);
}
logger.info("init mybatis");
}
public static synchronized SqlSession getSession() throws IOException {
SqlSession sqlSession =null;
if(null == sqlSession) {
sqlSession = (null != sqlSessionFactory)?sqlSessionFactory.openSession(): null;
}
return sqlSession;
}
}
代码中可以看到首先会通过??SessionFactory 去获取?SqlSession,里面会有一段 static 代码,在 SessionFactory 类加载时会执行 mybatis-config.xml 的初始化过程。现在我们直接看重点:?
通过 SqlSession 的?getMapper 获取 DataDao 数据接口的时候发现有 2 个实现,追溯到 SessionFactory 的初始化中会看到下面一段:
所以我们直接进入?DefaultSqlSession?getMapper 方法中,可以找到?MapperRegistry?中:
看到这里熟悉的动态代理的朋友知道这是一个获取 java 的动态代理类的写法(ps: 不熟悉动态代理的,可以看这里我的上一篇博客?动态代理白话解析),我们继续往下看:
继续走到下一步:dataDao.mysqlInsert(map); 执行这个方法时会调用?MapperProxy 的 invoke 方法:
![](https://img-
blog.csdnimg.cn/20190603143147684.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1JveWFsX2xy,size_16,color_FFFFFF,t_70)
开源看到除了 Object 中的方法不能代理,其他的都会走到 mapperMethod.execute(args):
type 字段会在 mapperMethod 初始化时赋值,这里我们执行的 insert,继续往下走到 DefaultSqlSession 的 update 方法?:
到这里后,接下来我们开始看?Configuration 的初始化,它在 SqlSessionFactory 初始化时构造的
上面 2 段代码可以看到 Configuration 是通过加载 mybatis-config.xml 时进行初始化的。
现在我们回到?this.configuration.getMappedStatement(statement); 这段代码:
进去看下我们发现他其实是通过 id 去获取对应的 MappedStatement ,通过上文我们可以知道 id 是 MapperMethod 的 execute 方法传进来的 commandName,它在 MapperMethod 的构造函数中的 setupFields 方法中初始化:
在本文中的值为:?DataDao.mysqlInsert。类似这种,也就是接口类.方法名。
现在我们回到主流程中,查看 mappedStatements 的初始化过程,是在上文中初始化 Configuration 的 this.mapperElement(root.evalNode("mappers")); 中? :?
这里会解析 mapper.xml 中的 sql 节点,本文中的示例是 这一段:
我们继续查看 mappedStatements 的初始化 :
这里的 id 我们可以看到就是节点中的 namespace.id 的值拼接,也就是?DataDao.mysqlInsert
现在我们继续回到主流程,看这段代码:
代码中通过 executor 执行 update 方法,现在我们看下 executor 的初始化过程,是在 SqlSession 初始化的过程中:
在 Configuration 的构造函数中可以看到这里的 executorType 默认为 SIMPLE,cacheEnabled 默认为 true.
现在我们继续回到主流程,进到 CachingExecutor 的?update 方法中:
这里会进入到?BaseExecutor 的 update 方法中
这里会进入到?SimpleExecutor 的 doUpdate 中:
这里首先初始化了一个?RoutingStatementHandler ,由上文可知?statementType 默认为?PREPARED,所以 其 delegate 属性为?PreparedStatementHandler。我们回到主流程,继续看? this.prepareStatement(handler) :
会走到 BaseStatementHandler 的 prepare 方法 :?
这里会进入到?PreparedStatementHandler 的?instantiateStatement 方法:
这里的 boundSql 是在 上文中的?mappedStatement 初始化的时候赋值的:
首先是在?parseStatementNode 中初始化 DynamicSqlSource,然后获取?BoundSql,接下来我们看下 XMLStatementBuilder 的 parseDynamicTags 方法:
代码中我们可以看到若节点类型既不是文本节点也不是 CDATA 节点就会调用不同的处理类去处理,我们比较常用的就是查询时使用的 <if> 和 <where> 节点?,这里我们执行的是 insert , 所以直接是一个文本节点,下面就是初始化了一个 MixedSqlNode 和?configuration 构造了?DynamicSqlSource,然后通过 getBoundSql 方法来构造 BoundSql:
评论