软件下载

mybatis工作原理(mybatis工作原理分析)

软件下载 投稿 2022-06-06 09:32:51 浏览

Mybatis工作原理也是面试的一大考点,必须要对其非常清晰,这样才能怼回去。本文建立在Spring+SpringMVC+Mybatis整合的项目之上。

我将其工作原理分为六个部分:

  1. 读取核心配置文件并返回InputStream流对象。
  2. 根据InputStream流对象解析出Configuration对象,然后创建SqlSessionFactory工厂对象
  3. 根据一系列属性从SqlSessionFactory工厂中创建SqlSession
  4. 从SqlSession中调用Executor执行数据库操作&&生成具体SQL指令
  5. 对执行结果进行二次封装
  6. 提交与事务

先给大家看看我的实体类:

面试官:你分析过mybatis工作原理吗?

 

1. 读取核心配置文件

1.1 配置文件mybatis-config.xml

面试官:你分析过mybatis工作原理吗?

 

当然,还有很多可以在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 或其他位置加载资源文件更加容易。

面试官:你分析过mybatis工作原理吗?

 

这个代码是根据Mybatis官方提供的一个不使用 XML 构建 SqlSessionFactory的一个Demo改编的。

注意:是官方给的一个不使用 XML 构建 SqlSessionFactory的例子,那么我们就从这个例子中查找入口来分析。

2. 根据配置文件生成SqlSessionFactory工厂对象

2.1
Resources.getResourceAsStream(resource);源码分析

Resources是mybatis提供的一个加载资源文件的工具类。

面试官:你分析过mybatis工作原理吗?

 

我们只看getResourceAsStream方法:

面试官:你分析过mybatis工作原理吗?

 

值的注意的是,它返回了一个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对象传入。

面试官:你分析过mybatis工作原理吗?

 

3. 创建SqlSession

SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。

public SqlSession openSession() {
 return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}

调用自身的openSessionFromDataSource方法:

  1. getDefaultExecutorType()默认是SIMPLE。
  2. 注意TX等级是 Null, autoCommit是false。
面试官:你分析过mybatis工作原理吗?

 

构建步骤:
Environment>>TransactionFactory+autoCommit+tx-level>>Transaction+ExecType>>Executor+Configuration+autoCommit>>SqlSession

其中,Environment是Configuration中的属性。

4. 调用Executor执行数据库操作&&生成具体SQL指令

在拿到SqlSession对象后,我们调用它的insert方法。

面试官:你分析过mybatis工作原理吗?

 

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();
 }
}

「真诚赞赏,手留余香」

求资源网

真诚赞赏,手留余香

使用微信扫描二维码完成支付

继续浏览有关编程的文章
发表评论
留言与评论(共有 0 条评论)
   
验证码:
版权声明

求资源网所发布的一切破解补丁,软件,以及其他分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途。
否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。