面试官:你分析过mybatis工作原理吗?
Mybatis工作原理也是面试的一大考点,必须要对其非常清晰,这样才能怼回去。本文建立在Spring+SpringMVC+Mybatis整合的项目之上。
我将其工作原理分为六个部分:
读取核心配置文件并返回
InputStream流对象。根据
InputStream流对象解析出Configuration对象,然后创建SqlSessionFactory工厂对象根据一系列属性从
SqlSessionFactory工厂中创建SqlSession从
SqlSession中调用Executor执行数据库操作&&生成具体SQL指令对执行结果进行二次封装
提交与事务
先给大家看看我的实体类:
/**
* 图书实体
*/
public class Book { private long bookId;// 图书ID private String name;// 图书名称 private int number;// 馆藏数量 getter and setter ...
}
1. 读取核心配置文件
1.1 配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://xxx.xxx:3306/ssm" />
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="BookMapper.xml"/>
</mappers>
</configuration>
当然,还有很多可以在XML 文件中进行配置,上面的示例指出的则是最关键的部分。要注意 XML 头部的声明,用来验证 XML 文档正确性。environment 元素体中包含了事务管理和连接池的配置。mappers 元素则是包含一组 mapper 映射器(这些 mapper 的 XML 文件包含了 SQL 代码和映射定义信息)。
1.2 BookMapper.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="Book">
<!-- 目的:为dao接口方法提供sql语句配置 -->
<insert id="insert" >
insert into book (name,number) values (#{name},#{number})
</insert>
</mapper>
就是一个普通的mapper.xml文件。
1.3 Main方法
从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。但是也可以使用任意的输入流(InputStream)实例,包括字符串形式的文件路径或者 file:// 的 URL 形式的文件路径来配置。
MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,可使从 classpath 或其他位置加载资源文件更加容易。
public class Main {
public static void main(String[] args) throws IOException {
// 创建一个book对象
Book book = new Book();
book.setBookId(1006);
book.setName("Easy Coding");
book.setNumber(110);
// 加载配置文件 并构建SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 从SqlSessionFactory对象中获取 SqlSession对象
SqlSession sqlSession = factory.openSession();
// 执行操作
sqlSession.insert("insert", book);
// 提交操作
sqlSession.commit();
// 关闭SqlSession
sqlSession.close();
}
}
这个代码是根据Mybatis官方提供的一个不使用 XML 构建 SqlSessionFactory的一个Demo改编的。
注意:是官方给的一个
不使用 XML 构建 SqlSessionFactory的例子,那么我们就从这个例子中查找入口来分析。
2. 根据配置文件生成SqlSessionFactory工厂对象
2.1 Resources.getResourceAsStream(resource);源码分析
Resources是mybatis提供的一个加载资源文件的工具类。

我们只看getResourceAsStream方法:
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream((ClassLoader)null, resource);
}
getResourceAsStream调用下面的方法:
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
throw new IOException("Could not find resource " + resource);
} else {
return in;
}
}
获取到自身的ClassLoader对象,然后交给ClassLoader(lang包下的)来加载:
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
ClassLoader[] arr$ = classLoader;
int len$ = classLoader.length;
for(int i$ = 0; i$ < len$; ++i$) {
ClassLoader cl = arr$[i$];
if (null != cl) {
InputStream returnValue = cl.getResourceAsStream(resource);
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue;
}
}
}
值的注意的是,它返回了一个InputStream对象。
2.2 new SqlSessionFactoryBuilder().build(inputStream);源码分析
public SqlSessionFactoryBuilder() { }
所以new SqlSessionFactoryBuilder()只是创建一个对象实例,而没有对象返回(建造者模式),对象的返回交给build()方法。
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
这里要传入一个inputStream对象,就是将我们上一步获取到的InputStream对象传入。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
// 进行XML配置文件的解析
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
;
}
}
return var5;
}
如何解析的就大概说下,通过Document对象来解析,然后返回InputStream对象,然后交给XMLConfigBuilder构造成org.apache.ibatis.session.Configuration对象,然后交给build()方法构造程SqlSessionFactory:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
3. 创建SqlSession
SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
调用自身的openSessionFromDataSource方法:
getDefaultExecutorType()默认是SIMPLE。
注意TX等级是 Null, autoCommit是false。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
// 根据Configuration的Environment属性来创建事务工厂
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
// 从事务工厂中创建事务,默认等级为null,autoCommit=false
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器
Executor executor = this.configuration.newExecutor(tx, execType);
// 根据执行器创建返回对象 SqlSession
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
构建步骤:Environment>>TransactionFactory+autoCommit+tx-level>>Transaction+ExecType>>Executor+Configuration+autoCommit>>SqlSession
其中,Environment是Configuration中的属性。
4. 调用Executor执行数据库操作&&生成具体SQL指令
在拿到SqlSession对象后,我们调用它的insert方法。
public int insert(String statement, Object parameter) {
return this.update(statement, parameter);
}
它调用了自身的update(statement, parameter)方法:
public int update(String statement, Object parameter) {
int var4;
try {
this.dirty = true;
MappedStatement ms = this.configuration.getMappedStatement(statement);
// wrapCollection(parameter)判断 param对象是否是集合
var4 = this.executor.update(ms, this.wrapCollection(parameter));
} catch (Exception var8) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + var8, var8);
} finally {
ErrorContext.instance().reset();
}
return var4;
}
mappedStatements就是我们平时说的sql映射对象.
源码如下:protected final Map<String, MappedStatement> mappedStatements;
可见它是一个Map集合,在我们加载xml配置的时候,mapping.xml的namespace和id信息就会存放为mappedStatements的key,对应的,sql语句就是对应的value.
然后调用BaseExecutor中的update方法:
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
this.clearLocalCache();
// 真正做执行操作的方法
return this.doUpdate(ms, parameter);
}
}
doUpdate才是真正做执行操作的方法:
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
int var6;
try {
Configuration configuration = ms.getConfiguration();
// 创建StatementHandler对象,从而创建Statement对象
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
// 将sql语句和参数绑定并生成SQL指令
stmt = this.prepareStatement(handler, ms.getStatementLog());
var6 = handler.update(stmt);
} finally {
this.closeStatement(stmt);
}
return var6;
}
先来看看prepareStatement方法,看看mybatis是如何将sql拼接合成的:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog);
// 准备Statement
Statement stmt = handler.prepare(connection);
// 设置SQL查询中的参数值
handler.parameterize(stmt);
return stmt;
}
来看看parameterize方法:
public void parameterize(Statement statement) throws SQLException {
this.parameterHandler.setParameters((PreparedStatement)statement);
}
这里把statement转换程PreparedStatement对象,它比Statement更快更安全。
这都是我们在JDBC中熟用的对象,就不做介绍了,所以也能看出来Mybatis是对JDBC的封装。
从ParameterMapping中读取参数值和类型,然后设置到SQL语句中:
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
if (parameterMappings != null) {
for(int i = 0; i < parameterMappings.size(); ++i) {
ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
Object value;
if (this.boundSql.hasAdditionalParameter(propertyName)) {
value = this.boundSql.getAdditionalParameter(propertyName);
} else if (this.parameterObject == null) {
value = null;
} else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
value = this.parameterObject;
} else {
MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = this.configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException var10) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
} catch (SQLException var11) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var11, var11);
}
}
}
}
}
5. 对查询结果二次封装
在doUpdate方法中,解析生成完新的SQL后,然后执行var6 = handler.update(stmt);我们来看看它的源码。
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
// 执行sql
ps.execute();
// 获取返回值
int rows = ps.getUpdateCount();
Object parameterObject = this.boundSql.getParameterObject();
KeyGenerator keyGenerator = this.mappedStatement.getKeyGenerator();
keyGenerator.processAfter(this.executor, this.mappedStatement, ps, parameterObject);
return rows;
}
因为我们是插入操作,返回的是一个int类型的值,所以这里mybatis给我们直接返回int。
如果是query操作,返回的是一个ResultSet,mybatis将查询结果包装程ResultSetWrapper类型,然后一步步对应java类型赋值等…有兴趣的可以自己去看看。
6. 提交与事务
最后,来看看commit()方法的源码。
public void commit() {
this.commit(false);
}
调用其对象本身的commit()方法:
public void commit(boolean force) {
try {
// 是否提交(判断是提交还是回滚)
this.executor.commit(this.isCommitOrRollbackRequired(force));
this.dirty = false;
} catch (Exception var6) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + var6, var6);
} finally {
ErrorContext.instance().reset();
}
}
如果dirty是false,则进行回滚;如果是true,则正常提交。
private boolean isCommitOrRollbackRequired(boolean force) {
return !this.autoCommit && this.dirty || force;
}
调用CachingExecutor的commit方法:
public void commit(boolean required) throws SQLException {
this.delegate.commit(required);
this.tcm.commit();
}
调用BaseExecutor的commit方法:
public void commit(boolean required) throws SQLException {
if (this.closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
} else {
this.clearLocalCache();
this.flushStatements();
if (required) {
this.transaction.commit();
}
}
}
最后调用JDBCTransaction的commit方法:
public void commit() throws SQLException {
if (this.connection != null && !this.connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + this.connection + "]");
}
// 提交连接
this.connection.commit();
}
}
Demo参考文档
http://www.mybatis.org/mybatis-3/zh/getting-started.html
最后,欢迎关注下方公众号:后端技术精选。每天一篇优质技术好文!
面试官:你分析过mybatis工作原理吗?的更多相关文章
- MyBatis工作原理
Mybatis工作原理: 我们的应用程序通过mybatis提供的api,增删改查方法来访问数据库,api底层调用了jdbc ,只不过mybatis对jdbc的封装是不完全封装,里面的sql语句需要我们 ...
- 从源码角度分析 MyBatis 工作原理
一.MyBatis 完整示例 这里,我将以一个入门级的示例来演示 MyBatis 是如何工作的. 注:本文后面章节中的原理.源码部分也将基于这个示例来进行讲解.完整示例源码地址 1.1. 数据库准备 ...
- Mybatis八( mybatis工作原理分析)
MyBatis的主要成员 Configuration MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中 SqlSession ...
- 面试官再问你 HashMap 底层原理,就把这篇文章甩给他看
前言 HashMap 源码和底层原理在现在面试中是必问的.因此,我们非常有必要搞清楚它的底层实现和思想,才能在面试中对答如流,跟面试官大战三百回合.文章较长,介绍了很多原理性的问题,希望对你有所帮助~ ...
- 面试官:RabbitMQ有哪些工作模式?
哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 今天又.又.又来面试了,还是老规 ...
- Mybatis工作原理(九)
mybatis工作流程: (1) SqlSessionFactoryBuilder 从 XML 配置文件或通过Java的方式构建出 SqlSessionFactory 的实例. (2) SqlSess ...
- MyBatis 工作原理
参考链接: 深入理解Mybatis原理:http://blog.csdn.net/luanlouis/article/details/40422941 MyBatis原理:http://www.jia ...
- 面试官:SpringBoot jar 可执行原理,知道吗?
文章篇幅较长,但是包含了SpringBoot 可执行jar包从头到尾的原理,请读者耐心观看.同时文章是基于 SpringBoot-2.1.3进行分析.涉及的知识点主要包括Maven的生命周期以及自定义 ...
- 从信息论的角度分析DNN的工作原理
在前面的文章里,使用神经网络的任意函数拟合性结合了一点黎曼几何的坐标系变化的知识,解释了神经网络是怎样根据输入x,计算出每个分类下的能量Ei(x)的,再之后使用能量模型推算出了概率,从而展示了理论上可 ...
随机推荐
- [深入学习Web安全](11)之XSS玩法
[深入学习Web安全](11)之XSS玩法 本文转自:i春秋社区 前言这篇paper,我们将学习如何优雅的调戏XSS.我们会教大家一些不常用的,但很实用的XSS姿势.我们在正式进入主题之前,先来说一下 ...
- Javascript高级编程学习笔记(42)—— DOM(8)Attr类型
Attr类型 我们在之前的文章中提到了,元素有一个 attributes 属性 该属性保存了一个 NamedNodeMap 集合 该集合中的元素也就是今天我们所要记叙的 attr 类型 主要就是方便我 ...
- Ubuntu18.04 VMwareTools安装方法
sudo apt-get upgradesudo apt-get install open-vm-tools-desktop -y sudo reboot 一.VMwareTools的一些实用性 ...
- Python面向对象5:类的常用魔术方法
魔术方法就是不需要人为调用的方法,基本是在特定的时刻自动触发- 魔术方法的统一的特征,方法名被前后各两个下滑线包裹 - 操作类 - `__init__`: 构造函数 - `__new__`: 对象实例 ...
- 吴恩达机器学习笔记6-梯度下降II(Gradient descent intuition)--梯度下降的直观理解
在之前的学习中,我们给出了一个数学上关于梯度下降的定义,本次视频我们更深入研究一下,更直观地感受一下这个算法是做什么的,以及梯度下降算法的更新过程有什么意义.梯度下降算法如下: 描述:对
- SpringBoot开源项目Jeeplatform
JEEPlatform 一款企业信息化开发基础平台,可以用于快速构建企业后台管理系统,集成了OA(办公自动化).SCM(供应链系统).ERP(企业资源管理系统).CMS(内容管理系统).CRM(客户关 ...
- 寒假小软件开发记录06--apk生成
先在strings.xml中修改了软件名称,再修改软件图标. 在Android模式下,进入Image Asset,进行图标的修改: android studio中,build->generate ...
- 图片格式PGM缩写
PGM是Portable Gray Map的缩写.它是灰度图像格式中一种最简单的格式标准.另外两种与之相近的图片格式是PBM和PPM.它们分别相应着黑白图像和彩色图像. PGM的数据存放方式相比于JP ...
- Python档案袋( Sys 与 Import 模块)
Sys模块: 获取Python有关的环境变量: import sys #得到Python的一些相关路径,环境变量 #其中site-packages目录存放的是一些第三方库 #其中lib目录存放的是一些 ...
- css3新特性总结
一.什么是css3 css用于控制网页的样式和布局,css3是css的升级版本,受浏览器限制,跨浏览器开发有点复杂.css3完全向后兼容 二.css3新特性 1.边框 css3的边框有如下属性: (1 ...