MyBatis 原码解析(version:3.2.7)
首先,我们看使用原生的JDBC来操作数据库的方式:
// 1. 获取JDBC Connection
Connection connection = DbManager.getConnectoin();
// 2. 组装sql语句
String sql = "insert into temp(id, name) values(?,?)";
// 3. 构造 PreparedStatement
PreparedStatement ps = connection.prepareStatement(sql);
// 4. 为 PreparedStatement 设置参数
ps.setLong(1, 1L);
ps.setString(2, "aaa");
// 5. 执行 PreparedStatement
int count = ps.executeUpdate();
那么,MyBatis是如何对上面的过程进行封装的呢?
我们以update为例,看MyBatis是如何封装这几个步骤的:
sql执行的入口:
DefaultSqlSession.update(String statement, Object parameter) --> Executor.update(MappedStatement ms, Object parameter) -->
SimpleExecutor.doUpdate(MappedStatement ms, Object parameter)
DefaultSqlSession.update(String, Object)
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement); // 将mapper中的sql解析成原生的JDBC能够识别的sql,其中包括MyBatis标签的解析,"#{key,JdbcType=XXX}"占位符替换成"?"
return executor.update(ms, wrapCollection(parameter)); // 构造java.sql.PreparedStatement --> 往java.sql.PreparedStatement设置sql的参数 --> 执行sql语句
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
可以看出,MyBatis将这个过程封装成了两步:
1. sql语句解析 (解析sql,替换占位符为"?")
2. sql语句执行 (设置sql中的参数,执行sql)
### 1. sql语句解析
configuration.getMappedStatement(statement)会去解析MyBatis的标签(比如:<foreach>、<if>等),并且将"#{key, JdbcType=XXX}"替换成原生的JDBC看的懂的占位符"?"。
流程如下:
Configuration.getMappedStatement(String, boolean) --> Configuration.buildAllStatements() --> MapperAnnotationBuilder.parseStatement(Method) -->
MapperAnnotationBuilder.buildSqlSourceFromStrings(String[], Class<?>, LanguageDriver) --> RawLanguageDriver.createSqlSource(Configuration, String, Class<?>)
--> XMLLanguageDriver.createSqlSource(Configuration, String, Class<?>)
20170721更新:(xml应该是如下)
Configuration.getMappedStatement(String, boolean) --> Configuration.buildAllStatements() --> XMLStatementBuilder.parseStatementNode() -->
LanguageDriver.createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) --> RawLanguageDriver.createSqlSource(Configuration, String, Class<?>)
--> XMLLanguageDriver.createSqlSource(Configuration, String, Class<?>)
可以看出,MyBatis中的动态SQL解析是通过别名为 xml 语言驱动器 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver 驱动解析的。
public class XMLLanguageDriver implements LanguageDriver { public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
} public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
} public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
if (script.startsWith("<script>")) { // issue #3
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
} else {
script = PropertyParser.parse(script, configuration.getVariables()); // issue #127
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) { // 动态sql解析入口
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
} }
DynamicSqlSource.java
public class DynamicSqlSource implements SqlSource { private Configuration configuration;
private SqlNode rootSqlNode; public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
} public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject); // 构造DynamicContext,并将DynamicContext中的属性ContextMap bindings赋值
rootSqlNode.apply(context); // 处理sql中的标签,比如:<foreach>、<if>等
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); // 将占位符"#{key, jdbcType=XXX}"替换成占位符"?"。也就是原生的java.sql.PreparedStatement支持的占位符。(具体是通过GenericTokenParser#parse(String text)来实现的)
BoundSql boundSql = sqlSource.getBoundSql(parameterObject); // 构造BoundSql
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); // 将参数的key和value存放至BoundSql的MetaObject metaParameters;中
}
return boundSql;
} }
####2. sql语句执行
通过SimpleExecutor.java 去执行sql语句。其中,最重要的两步是 1. prepareStatement(handler, ms.getStatementLog()); 2. handler.update(stmt);
public class SimpleExecutor extends BaseExecutor { public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
} // 所有的数据库更新操作,都会调用doUpdate(),包括insert、update、delete
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); // 构造执行Statement的StatementHandler
stmt = prepareStatement(handler, ms.getStatementLog()); // 通过java.sql.Connection拿到java.sql.PreparedStatement,并且为 java.sql.PreparedStatement 设置sql中的参数
return handler.update(stmt); // 代理执行 java.sql.PreparedStatement (PreparedStatementHandler.update(Statement))
} finally {
closeStatement(stmt);
}
} // 所有的数据库查询操作,都用调用doQuery()
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
} public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
return Collections.emptyList();
} private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection); // 通过 java.sql.Connection 拿到 java.sql.PreparedStatement (PreparedStatementHandler.instantiateStatement(Connection))
handler.parameterize(stmt); // 为 java.sql.PreparedStatement 设置sql中的参数。(DefaultParameterHandler.setParameters(PreparedStatement))
return stmt;
} }
至此,MyBatis的封装就一目了然了。^_^
附:
MyBatis开发文档: http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html
MyBatis 原码解析(version:3.2.7)的更多相关文章
- 【MyBatis源码解析】MyBatis一二级缓存
MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...
- mybatis源码-解析配置文件(三)之配置文件Configuration解析
目录 1. 简介 1.1 系列内容 1.2 适合对象 1.3 本文内容 2. 配置文件 2.1 mysql.properties 2.2 mybatis-config.xml 3. Configura ...
- Mybatis源码解析,一步一步从浅入深(一):创建准备工程
Spring SpringMVC Mybatis(简称ssm)是一个很流行的java web框架,而Mybatis作为ORM 持久层框架,因其灵活简单,深受青睐.而且现在的招聘职位中都要求应试者熟悉M ...
- Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例
在Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们看到了XMLConfigBuilder(xml配置解析器)的实例化.而且这个实例化过程在文章:Mybatis源码解析,一步一步从浅 ...
- Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析
在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...
- Mybatis源码解析-DynamicSqlSource和RawSqlSource的区别
XMLLanguageDriver是ibatis的默认解析sql节点帮助类,其中的方法其会调用生成DynamicSqlSource和RawSqlSource这两个帮助类,本文将对此作下简单的简析 应用 ...
- mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)
目录 1. 简介 2. 解析 3 StrictMap 3.1 区别HashMap:键必须为String 3.2 区别HashMap:多了成员变量 name 3.3 区别HashMap:key 的处理多 ...
- mybatis源码-解析配置文件(四)之配置文件Mapper解析
在 mybatis源码-解析配置文件(三)之配置文件Configuration解析 中, 讲解了 Configuration 是如何解析的. 其中, mappers作为configuration节点的 ...
- Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码
在文章:Mybatis源码解析,一步一步从浅入深(一):创建准备工程,中我们为了解析mybatis源码创建了一个mybatis的简单工程(源码已上传github,链接在文章末尾),并实现了一个查询功能 ...
随机推荐
- 如何检查CentOS服务器受到DDOS攻击
登录到你的服务器以root用户执行下面的命令,使用它你可以检查你的服务器是在DDOS攻击与否: netstat -anp |grep 'tcp\|udp' | awk '{print $5}' | c ...
- hbase源码系列(一)Balancer 负载均衡
看源码很久了,终于开始动手写博客了,为什么是先写负载均衡呢,因为一个室友入职新公司了,然后他们遇到这方面的问题,某些机器的硬盘使用明显比别的机器要多,每次用hadoop做完负载均衡,很快又变回来了. ...
- 使用VMware将Linux装在物理硬盘上,开机即可进入Linux
我的笔记本上是双系统Windows和Linux .经常需要在两个系统间切换,很麻烦.于是我想到如果能用VMWare虚拟机加载物理硬盘中的系统,可以在需要时,直接启动另外一个系统. 注意:我的双 ...
- 关于Unity3D中鼠标移动指定物体的解决方案
一.鼠标拾取物体的原理 在Unity3D当中,想要在观察面(Aspect)中拾取物体(有碰撞属性)的方法一般如下: 1.声明一个观察的摄像机.一个从摄像机原点出发的射线Ray以及一个用于检测碰撞的Ra ...
- WEB打印(jsp版)
<%@ page contentType="text/html; charset=UTF-8" language="java"%> <%@in ...
- 【CSS】网页样式记录
一.按钮 p.tiy { display: inline-block; margin-top: 15px; margin-bottom: 10px; vertical-align: middle; t ...
- (笔记)linux增加非标波特率的方法
1.内核修改 涉及到的内核文件包括driver/char/tty_ioctl.c和arch/xx/include/asm/termbits.h 在linux内核中,struct ktermios结构的 ...
- CodeCombat森林关卡Python代码
地牢关卡过完,接下来是边缘的森林! 1,森林保卫战 hero.moveUp() hero.buildXY("fence", 40, 52) hero.moveDown() hero ...
- 函数后面有个 const
错误场景:类中的重载函数 编译报错: 函数后面加了 const 就好了. 非静态成员函数后面加const(加到非成员函数或静态成员后面会产生编译错误), 表示成员函数隐含传入的this指针为 cons ...
- Json---Linux下使用Jsoncpp
一.安装 scons 下载地址:http://sourceforge.net/projects/scons/files/scons/2.1.0/scons-2.1.0.tar.gz/download ...