导语

在前一篇文章Mybatis源码解析《一》中,已经简单了捋了一下mybatis核心文件和mapper配置文件的一个基本的解析流程,这是理解mybatis的基本,和spring中的配置文件的加载解析是一样的道理。

既然说完了文件的加载,那么接下来便是关于mybatis的核心流程了,SqlSession的创建、SQL语句的操作、session的commit和关闭。在正式开始之前先回顾下上一篇文章中的测试代码。

public class MybatisTest {@Testpublic void test() throws Exception {User user = new User();user.setAddress("北京市海淀区");user.setBirthday(new Date(2000-10-01));user.setSex("男");user.setUsername("李清源");InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();sqlSession.insert("insertUser", user);sqlSession.commit();sqlSession.close();}
}

本篇文章将从SqlSession sqlSession = sqlSessionFactory.openSession()这行代码开始,那么就不多说了,开始mybatis源码的旅行了。

二、创建SqlSession对象

从代码中可以得知,sqlSesssion是通过sqlSessionFactory去打开session的,那就看下代码具体是如何体现的:

DefaultSqlSessionFactory.java
  public SqlSession openSession() {// 通过数据源打开session,第一个参数是通过configuration获取默认的执行器类型(这个configuration贯穿整个mybatis的执行),对于它的作用,后面看return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}

这段代码中调用openSessionFromDataSource方法,方法中传入默认执行器类型、null、和false,这几个参数的意义分别是:默认执行器类型、事务隔离级别、是否自动提交数据库。继续看这个调用方法的代码:

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {// 通过configuration获取开发环境(这个在核心配置文件中在environments有个environment标签)final Environment environment = configuration.getEnvironment();// 通过environment获取事务工厂(在我上面那段配置文件中写的是JDBC)final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);// 通过环境中获取的数据源、事务的隔离级别、是否自动提交来创建一个事务tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);// configuration通过事务和执行器类型来创建一个执行器final Executor executor = configuration.newExecutor(tx, execType);// 最后通过configuration(配置对象)、执行器、是否自动提交创建一个DefaultSqlSession返回return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

这段代码中还算比较清晰的,在这段代码中,它的基本流程有如下几步:

  1. 通过configuration获取开发环境(这个在核心配置文件中在environments有个environment标签)
  2. 然后再通过上面所得到的environment获取事务工厂(在我前面文章中那段配置文件中写的是JDBC)
  3. 通过环境中获取的数据源、事务的隔离级别、是否自动提交这几个参数来创建一个事务
  4. configuration通过事务和执行器类型来创建一个执行器
  5. 最后通过configuration(配置对象)、执行器、是否自动提交创建一个DefaultSqlSession返回

三、操作SQL语句

通过sqlSessionFactory去打开sqlSession后,便可以通过sqlSession来操作SQL语句了,但是这里所要考虑的是通过这个操作是怎么操作数据的,那就带着这个疑问来看代码中是怎么实现的。

  public int insert(String statement, Object parameter) {// 这里传入的两个参数是sqlSession.insert("insertUser", user)这行代码所传的第一个参数"insertUser",这个参数是SQL命令的id编号,也就是mapper配置文件中所对应的,// 第二个参数是user对象return update(statement, parameter);}

上面提到了通过insert这个方法是怎么最后实现数据库的操作的,说到这里,从上面代码中来看,来把问题细分一下。在注释里说了"insertUser"这个参数的值是mapper文件中,所对应的标签id,因此在这里首先就要思考下这个id和SQL语句是如何进行定位的?当定位好之后对于所传入的user对象中的数据应该如何与SQL命令进行绑定呢?最后在数据与SQL命令绑定好之后,这样的SQL命令是如何输送的呢?

带着上面的这样的几个问题,来继续看代码中是如何实现的。首先来看下上面代码中所调用的update(statement, parameter)方法:

  public int update(String statement, Object parameter) {try {// 这里算是首次出现dirty,这个意思是脏的意思,是为了把SQLSession设置为true,这是个关注点dirty = true;// 通过所传入的参数statement,由configuration对象来获取mappedStatement并赋值给MappedStatement对象MappedStatement ms = configuration.getMappedStatement(statement);// 然后通过所获取到的mappedStatement和wrapCollection(parameter)锁处理后的object对象交由执行器执行return executor.update(ms, wrapCollection(parameter));} catch (Exception e) {throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

上面的代码中大体就三步:

  • 设置dirty为true,这个作用后面再说
  • 通过所传入的参数statement,由configuration对象来获取mappedStatement并赋值给MappedStatement对象
  • 然后通过所获取到的mappedStatement和wrapCollection(parameter)锁处理后的object对象交由执行器执行

这三步中的第二步锁获取到的结果最后所赋值给ms的这个对象的结果如下图:

从上图中,可以清晰的看出通过所传入的参数,然后又这个参数定位这个SQL的位置,然后再找到这个SQL配置文件中的真正SQL语句。

Configuration.java
  public MappedStatement getMappedStatement(String id) {return this.getMappedStatement(id, true);}
  public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {if (validateIncompleteStatements) {// 这个方法是做各种校验的,但是按照现在的测试代码来看,是基本上都不走的buildAllStatements();}// 通过id在缓存中获取Statement相关数据return mappedStatements.get(id);}
    public V get(Object key) {V value = super.get(key);if (value == null) {throw new IllegalArgumentException(name + " does not contain value for " + key);}if (value instanceof Ambiguity) {throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name+ " (try using the full name including the namespace, or rename one of the entries)");}return value;}

上面的代码,就是configuration.getMappedStatement(statement)的整个执行流程,其实就是把之前mapper文件所解析缓存的内容,此时再取出来。

在得到mappedStatement对象后,按照一开始的几个问题来看,这里的SQL定位已经完成了,那么现在要做的就是SQL模板的赋值了。那继续看executor.update(ms, wrapCollection(parameter))方法中的具体操作。在看update方法之前先看下wrapCollection这个方法中做了什么。

  private Object wrapCollection(final Object object) {// 判断所传入的对象是否是集合,是的话就走当前的逻辑if (object instanceof Collection) {StrictMap<Object> map = new StrictMap<Object>();map.put("collection", object);if (object instanceof List) {map.put("list", object);}return map;} else if (object != null && object.getClass().isArray()) {StrictMap<Object> map = new StrictMap<Object>();map.put("array", object);return map;}return object;}

因为前面所传入的参数,既不是集合也不是数组,所以这里返回的还是原对象。那就来看update方法。

BaseExecutor.java
  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);}

这段代码很简单,但是还是没有到达我们的核心代码中,那只有继续看核心入口doUpdate(ms, parameter)方法的具体实现了。

SimpleExecutor.java
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {// 总算看到点希望了,在JDBC里面所有的SQL都是依赖Statement去输送的Statement stmt = null;try {// 从前面所传入的mappedStatement中来获取configuration配置对象Configuration configuration = ms.getConfiguration();// 通过this(SimpleExecutor)、ms(sql)、parameter(传入的对象)、行绑定等来创建一个StatementHandler对象StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);// 这里是把所传入的对象的数据和SQL模板进行一个绑定合成一句完整的SQLstmt = prepareStatement(handler, ms.getStatementLog());// 最后执行SQLreturn handler.update(stmt);} finally {closeStatement(stmt);}}

这段代码中主要的两个作用就是把所传入的对象的数据和SQL模板进行一个绑定合成一句完整的SQL和执行SQL操作数据库,那先看下prepareStatement(handler, ms.getStatementLog())方法:

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;// 获取链接信息Connection connection = getConnection(statementLog);// 准备stmtstmt = handler.prepare(connection, transaction.getTimeout());// 最后参数绑定整合handler.parameterize(stmt);return stmt;}

先看下准备stmt的过程,这里代码很简单。

  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());Statement statement = null;try {// 实例化statementstatement = instantiateStatement(connection);// 设置statement超时时间setStatementTimeout(statement, transactionTimeout);// 设置fetch的sizesetFetchSize(statement);return statement;} catch (SQLException e) {closeStatement(statement);throw e;} catch (Exception e) {closeStatement(statement);throw new ExecutorException("Error preparing statement.  Cause: " + e, e);}}

接下来看下最后参数的整合绑定,核心代码如下:

DefaultParameterHandler.java
  public void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != null) {for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional paramsvalue = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}TypeHandler typeHandler = parameterMapping.getTypeHandler();JdbcType jdbcType = parameterMapping.getJdbcType();if (value == null && jdbcType == null) {jdbcType = configuration.getJdbcTypeForNull();}try {typeHandler.setParameter(ps, i + 1, value, jdbcType);} catch (TypeException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);} catch (SQLException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);}}}}}}

这里的代码相对比较复杂,感兴趣的同学可以自己debug进来研究一下,这里就是最后数据的绑定SQL模板的过程了。在看完第二个问题后,那来看看最后一个问题,就是SQL的执行了。

PreparedStatementHandler.java
  public int update(Statement statement) throws SQLException {// 把statement赋给PreparedStatementPreparedStatement ps = (PreparedStatement) statement;// 最后做执行操作ps.execute();// 把执行结果返回int rows = ps.getUpdateCount();Object parameterObject = boundSql.getParameterObject();KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);return rows;}

写着写着发现篇幅又很长了,看来后面的操作的代码解析只能放到下篇文章去做解析了,这有点超出我的所预想的,那本篇文章就先到这里了,感谢大家的阅读,如有错误请大家指出,谢谢!

Mybatis源码解析《二》相关推荐

  1. Mybatis源码解析(一):环境搭建

    Mybatis源码系列文章 手写源码(了解源码整体流程及重要组件) Mybatis源码解析(一):环境搭建 Mybatis源码解析(二):全局配置文件的解析 Mybatis源码解析(三):映射配置文件 ...

  2. mybatis源码阅读(二):mybatis初始化上

    转载自  mybatis源码阅读(二):mybatis初始化上 1.初始化入口 //Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与 ...

  3. mybatis源码解析(一)

    Mybatis 源码解析 (一) 一. ORM框架的作用 实际开发系统时,我们可通过JDBC完成多种数据库操作.这里以传统JDBC编程过程中的查询操作为例进行说明,其主要步骤如下: (1)注册数据库驱 ...

  4. 对标阿里P8的MyBatis源码解析文档,面试/涨薪两不误,已献出膝盖

    移动互联网的特点是大数据.高并发,对服务器往往要求分布式.高性能.高灵活等,而传统模式的Java数据库编程框架已经不再适用了. 在这样的背景下,一个Java的持久框架MyBatis走入了我们的世界,它 ...

  5. 【MyBatis源码解析】MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...

  6. mybatis源码解析一 xml解析(解析器)

    最近闲来无事,看着一些源码类的书籍,只是光看,好像并不能给自己很好的益处,无法记下来,所以就有了这个Mybatis源码解析系列的博客.网上也有大量的源码解析,在此记录有两个原因,一是为了加深自己的印象 ...

  7. mybatis源码考究二(sqlsession线程安全和缓存失效)

    mybatis源码考究二 1.mybatis整合spring解决sqlsession线程安全问题 2.mybatis整合spring一级缓存失效问题 mybatis结合spring使用 1.项目依赖 ...

  8. 【深度学习模型】智云视图中文车牌识别源码解析(二)

    [深度学习模型]智云视图中文车牌识别源码解析(二) 感受 HyperLPR可以识别多种中文车牌包括白牌,新能源车牌,使馆车牌,教练车牌,武警车牌等. 代码不可谓不混乱(别忘了这是职业公司的准产品级代码 ...

  9. mybatis源码解析1_sessionFactory

    注 一下内容都是根据尚硅谷2018年mybatis源码解析做的课程笔记,视频链接: https://www.bilibili.com/video/BV1mW411M737?p=74&share ...

最新文章

  1. python3.6.8卸载_CentOS7下安装python3.6.8的教程详解
  2. 配置MySQL的环境变量
  3. 《JAVA与模式》之装修者模式
  4. 织物印花疵点专用术语大全
  5. tcpprep man 手册翻译
  6. 浪潮信息能制造超级计算机吗,浪潮信息为中国航天探索事业出力 高性能计算技术领衔...
  7. HTML5汽车赛道飙车游戏免费源码下载
  8. 基于汽车运动学模型的LQR控制
  9. 51单片机驱动红外编解码模块(YS-IRTM)
  10. 尚德机构营收同比大涨22.9%,高增长可持续吗?
  11. Win10中Jenkins的安装和基本使用流程
  12. 2022年装饰行业市场分析
  13. Golang中用到的的Websocket库
  14. 爬取链家所有房源信息(在售、成交、租房)
  15. 考研英语二2017阅读理解Text3
  16. Terracotta简介
  17. matlab 画渐近线,如何绘制渐近线?
  18. IIS的ASP木马怎么上传的
  19. 计算机不能上网查找原因的步骤,电脑不能上网的原因及操作步骤_电脑突然不能上网...
  20. win7升级win10方法

热门文章

  1. es android,ES文件浏览器
  2. Windows系统设置宽带连接开机自动拨号的方法
  3. C语言中的 while 循环
  4. Java使用ftl模板文件生成Word,以及Word转换图片或Pdf工具类
  5. 中级育婴师证怎么考,需要些什么条件
  6. java的tey语句return了_Java finally语句到底是在return之前还是之后执行?
  7. 操作系统真象还原——3.完善MBR
  8. 中芯微761的随身WiFi怎么切卡去除后门
  9. 程序员必备的5大有趣编程网站,学编程就像玩游戏一样!
  10. Windows10怎么用计算机,Win10重置此电脑怎么用