带你揭开mybatis源码的层层面纱

目录

概述

架构设计

Mybatis的调用链路简介 

构件描述

总体流程

源码剖析

读取xml配置文件

源码分析-执⾏SQL流程

先简单介绍SqlSession :

源码剖析-executor

源码剖析-StatementHandler

Mapper代理⽅式:

源码剖析-getmapper()


概述

之前的文章写过一篇自定义持久层框架,实现了Mybatis相似的功能。如果想快速掌握mybatis,强烈推荐简单阅读一下这篇文章。

自定义持久层框架

源码地址

https://gitee.com/li_gang_x/my-project-model

有了上面的基础,阅读mybatis源码就不那么陌生了,你会有些许熟悉,似曾相识的感觉。下面我将带领读者进入到mybatis框架,领略大佬们是怎么实现这个持久层框架的,怎么优雅的实现。废话不多说,下面进入主题。


架构设计

要想 整体把握一个框架,不管要属性怎么使用,而且要知其所以然。整体把握一个框架,首先就要对框架整体有一个认知。  

我们把 Mybatis 的功能架构分为三层:
(1) API 接⼝层:提供给外部使⽤的接⼝ API ,开发⼈员通过这些本地 API 来操纵数据库。接⼝层⼀接收到调⽤请求就会调⽤数据处理层来完成具体的数据处理。
MyBatis 和数据库的交互有两种⽅式:
  • a. 使⽤传统的MyBatis提供的API
  • b. 使⽤Mapper代理的⽅式(生成代理对象进行实现)
(2) 数据处理层:负责具体的 SQL 查找、 SQL 解析、 SQL 执⾏和执⾏结果映射处理等。它主要的⽬的是根据调⽤的请求完成⼀次数据库操作。
(3) 基础⽀撑层:负责最基础的功能⽀撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共⽤的东⻄,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的⽀撑

Mybatis的调用链路简介 

构件描述

  • SqlSession
        作为MyBatis ⼯作的主要顶层 API ,表示和数据库交互的会话,完成必要数
        据库增删改查功能
  • Executor
        MyBatis执⾏器,是 MyBatis 调度的核⼼,负责 SQL 语句的⽣成和查询缓存的维护
  • StatementHandler
        封装了JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参数、将Statement 结果集转换成List 集合。
  • ParameterHandler
        负责对⽤户传递的参数转换成JDBC Statement 所需要的参数,
  • ResultSetHandler
        负责将JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合;
  • TypeHandler
        负责java 数据类型和 jdbc 数据类型之间的映射和转换
  • MappedStatement
        MappedStatement维护了⼀条< select | update | delete | insert >节点的封装
  • SqlSource

        负责根据用户传递的parameterObject,动态生成Sql语句,将信息封装到BoundSql对象中并返回

  • BoundSql
        表示动态⽣成的SQL 语句以及相应的参数信息

总体流程

  1. 加载配置并初始化
  2. 接收调⽤请求
  3. 处理操作请求
  4. 返回处理结果
 
(1) 加载配置并初始化
触发条件: 加载配置⽂件
配置来源于两个地⽅,
⼀个是配置⽂件 ( 主配置⽂件 conf.xml,mapper ⽂件 *.xml),
个是 java 代码中的 注解,
主配置⽂件 内容解析封装到 Configuration ,
sql的配置信息 加载成为⼀个 mappedstatement 对象,存储在内存之中
(2) 接收调⽤请求
触发条件 :调⽤ Mybatis 提供的 API
传⼊参数:为 SQL ID 和传⼊参数对象
处理过程: 将请求传递给下层的请求处理层进⾏处理。
(3) 处理操作请求
触发条件: API 接⼝层传递请求过来
传⼊参数:为 SQL ID 和传⼊参数对象
处理过程:
  • (A) 根据SQLID查找对应的MappedStatement对象。
  • (B) 根据传⼊参数对象解析MappedStatement对象,得到最终要执⾏的SQL和执⾏传⼊参数。
  • (C) 获取数据库连接,根据得到的最终SQL语句和执⾏传⼊参数到数据库执⾏,并得到执⾏结果。
  • (D) 根据MappedStatement对象中的结果映射配置对得到的执⾏结果进⾏转换处理,并得到最终的处理结果。
  • (E) 释放连接资源。
(4) 返回处理结果
将最终的处理结果返回
 

源码剖析

读取xml配置文件

public class SqlSessionFactoryBuilder {

  //SqlSessionFactoryBuilder有9个build()方法
  //发现mybatis文档老了,http://www.mybatis.org/core/java-api.html,关于这块对不上

  //以下3个方法都是调用下面第4种方法
  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }

  //第4种方法是最常用的,它使用了一个参照了XML文档或更特定的SqlMapConfig.xml文件的Reader实例。
  //可选的参数是environment和properties。Environment决定加载哪种环境(开发环境/生产环境),包括数据源和事务管理器。
  //如果使用properties,那么就会加载那些properties(属性配置文件),那些属性可以用${propName}语法形式多次用在配置文件中。和Spring很像,一个思想?
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        //委托XMLConfigBuilder来解析xml文件,并构建
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
        //这里是捕获异常,包装成自己的异常并抛出的idiom?,最后还要reset ErrorContext
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  //以下3个方法都是调用下面第8种方法
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  //第8种方法和第4种方法差不多,Reader换成了InputStream
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
    
  //最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}

 有通过Reader获取和通过Io流获取的两种方法,下面主要写通过输入流获取的方法

Inputstream inputstream = Resources.getResourceAsStream("mybatisconfig.xml");
 //这⼀⾏代码正是初始化⼯作的开始。
 SqlSessionFactory factory = new
SqlSessionFactoryBuilder().build(inputStream);
//第8种方法和第4种方法差不多,Reader换成了InputStream
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  //最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
MyBatis 在初始化的时候,会将 MyBatis 的配置信息全部加载到内存中,使⽤
org.apache.ibatis.session.Configuration 实例来维护
下⾯进⼊对配置⽂件解析部分:
⾸先对 Configuration 对象进⾏介绍:
 
Configuration 对象的结构和 xml 配置⽂件的对象⼏乎相同。
回顾⼀下 xml 中的配置标签有哪些:
properties ( 属性 ) settings ( 设置 ) typeAliases ( 类型别名 ) typeHandlers ( 类型处理
) objectFactory ( 对象⼯⼚ ) mappers ( 映射器 ) Configuration 也有对应的对象属性来封
装它们
也就是说,初始化配置⽂件信息的本质就是创建 Configuration 对象,将解析的 xml 数据封装到
Configuration 内部属性中

  <?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="${driver}"/> 
  <property name="url" value="${url}"/> 
  <property name="username" value="${username}"/> 
  <property name="password" value="${password}"/> 
  </dataSource> 
  </environment> 
  </environments>
  <mappers> 
  <mapper resource="org/mybatis/example/BlogMapper.xml"/> 
  </mappers> 
  </configuration>

  

  //解析配置
  public Configuration parse() {
    //如果已经解析过了,报错
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;

    
    //根节点是configuration
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
//解析配置
  private void parseConfiguration(XNode root) {
    try {
      //分步骤解析
      //issue #117 read properties first
      //1.properties
      propertiesElement(root.evalNode("properties"));
      //2.类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //3.插件
      pluginElement(root.evalNode("plugins"));
      //4.对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      //5.对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.设置
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //7.环境
      environmentsElement(root.evalNode("environments"));
      //8.databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9.类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10.映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

 

介绍⼀下 MappedStatement
作⽤: MappedStatement Mapper 配置⽂件中的⼀个 select/update/insert/delete 节点相对应。
mapper 中配置的标签都被封装到了此对象中,主要⽤途是描述⼀条 SQL 语句。
初始化过程: 回顾刚开始介绍的加载配置⽂件的过程中,会对 mybatis-config.xm l 中的各个标签都进⾏解析,其中有mappers 标签⽤来引⼊ mapper.xml ⽂件或者配置 mapper 接⼝的⽬录。
<select id="getUser" resultType="user" >
 select * from user where id=#{id}
</select>

这样的⼀个select标签会在初始化配置⽂件时被解析封装成⼀个MappedStatement对象,然后存储在 Configuration对象的mappedStatements属性中,mappedStatements 是⼀个HashMap,存储时

key =全限定类名+⽅法名,

value =对应的MappedStatement对象。

  • configuration中对应的属性为
Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement> ("Mapped Statements collection")
XMLConfigBuilder 中的处理:
private void parseConfiguration(XNode root) {
    try {
      //省略其他标签的处理
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration.Cause:" + e, e);
    }
  }
到此对 xml 配置⽂件的解析就结束了,回到步骤 2. 中调⽤的重载 build ⽅法
// 5.调⽤的重载⽅法
public SqlSessionFactory build(Configuration config) {
 //创建了 DefaultSqlSessionFactory 对象,传⼊ Configuration 对象。
 return new DefaultSqlSessionFactory(config);
}

源码分析-执⾏SQL流程

先简单介绍SqlSession

SqlSession 是⼀个接⼝,
它有两个实现类: DefaultSqlSession ( 默认 ) 和 SqlSessionManager (弃⽤,不做介绍 )
SqlSession是MyBatis中⽤于和数据库交互的顶层类,通常将它与ThreadLocal绑定,⼀个会话使⽤⼀ 个SqlSession,并且在使⽤完毕后需要close
public class DefaultSqlSession implements SqlSession {

  private Configuration configuration;
  private Executor executor;
...
}
SqlSession 中的两个最重要的参数, configuration 与初始化时的相同, Executor 为执⾏器
 
Executor
Executor 也是⼀个接⼝,他有三个常⽤的实现类:
  • BatchExecutor (重⽤语句并执⾏批量更新)
  • ReuseExecutor (重⽤预处理语句 prepared statements)
  • SimpleExecutor (普通的执⾏器,默认)
继续分析,初始化完毕后,我们就要执⾏ SQL
首先获取sqlSession
SqlSession sqlSession = factory.openSession();
List<User> list =
sqlSession.selectList("com.lagou.mapper.UserMapper.getUserByName");

进入到openSession方法

//6. 进⼊ openSession ⽅法。
 public SqlSession openSession() {
 //getDefaultExecutorType()传递的是SimpleExecutor
 return openSessionFromDataSource(configuration.getDefaultExecutorType(), null,
false);
 }

可以看到调用的是openSessionFormDataSource

//7. 进⼊ penSessionFromDataSource
//ExecutorType Executor 的类型, TransactionIsolationLevel 为事务隔离级别,
autoCommit 是否开启事务
//openSession 的多个重载⽅法可以指定获得的 SeqSession Executor 类型和事务的处理
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //通过事务工厂来产生一个事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //生成一个执行器(事务包含在执行器里)
      final Executor executor = configuration.newExecutor(tx, execType);
      //然后产生一个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();
    }
  }
执⾏ sqlsession 中的 api
//8.进⼊selectList⽅法,多个重载⽅法
  @Override
  public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  //核心selectList
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //根据statementId(全限定名+⽅法名)找到对应的MappedStatement 根据传⼊的全限定名+⽅法名从映射的Map中取出MappedStatement对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      //调⽤Executor中的⽅法处理
      //转而用执行器来查询结果,注意这里传入的ResultHandler是null
      //RowBounds是⽤来逻辑分⻚
      // wrapCollection(parameter)是⽤来装饰集合或者数组参数
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

源码剖析-executor

继续源码中的步骤,进⼊ executor.query()
 //SqlSession.selectList会调用此方法
//  继续源码中的步骤,进⼊executor.query()
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //根据传⼊的参数动态获得SQL语句,最后返回⽤BoundSql对象表示
    BoundSql boundSql = ms.getBoundSql(parameter);
    //创建缓存Key 为本次查询创建缓存的Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    //查询
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }
进⼊ query 的重载⽅法中
@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    //如果已经关闭,报错
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //先清局部缓存,再查询.但仅查询堆栈为0,才清。为了处理递归调用
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      //加一,这样递归调用到上面的时候就不会再清局部缓存了
      queryStack++;
      //先根据cachekey从localCache去查
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //若查到localCache缓存,处理localOutputParameterCache
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //从数据库查 如果缓存中没有本次查找的值,那么从数据库中查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      //清空堆栈
      queryStack--;
    }
    if (queryStack == 0) {
      //延迟加载队列中所有元素
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      //清空延迟加载队列
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
    	//如果是STATEMENT,清本地缓存
        clearLocalCache();
      }
    }
    return list;
  }

下面进入到从数据库查询的逻辑

//从数据库查
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //先向缓存中放入占位符???
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //查询的⽅法
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      //最后删除占位符
      localCache.removeObject(key);
    }
    //将查询结果放⼊缓存
    localCache.putObject(key, list);
    //如果是存储过程,OUT参数也加入缓存
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

这里有个doQuery 方法阅读过源码的同学都知道,do开头的一般是真正执行逻辑的方法

//query-->queryFromDatabase-->doQuery
  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

这个方法是protected修饰的,是子类实现

SimpleExecutor 中实现⽗类的 doQuery 抽象⽅法
//select
  @Override
  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
      //这里看到ResultHandler传入了
      //传⼊参数创建StatementHandler对象来执⾏查询
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //准备语句 创建jdbc中的statement对象
      stmt = prepareStatement(handler, ms.getStatementLog());
      //StatementHandler.query
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

下面查看prepareStatement方法的实现

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        //代码中的getConnection⽅法经过重重调⽤最后会调⽤openConnection⽅法,从连接池中获得连接
        Connection connection = getConnection(statementLog);
        //调用StatementHandler.prepare
        stmt = handler.prepare(connection);
        //调用StatementHandler.parameterize
        handler.parameterize(stmt);
        return stmt;
    }

这里的连接是 JdbcTransaction 中从连接池中获取的连接

/**
   * 从连接池获得连接的⽅法
   * @throws SQLException
   */
  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    //从连接池获得连接
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommmit);
  }

上述的Executor.query()⽅法⼏经转折,最后会创建⼀个StatementHandler对象,然后将必要的参数传递给 StatementHandler,使⽤StatementHandler来完成对数据库的查询,最终返回List结果集。

从上⾯的代码中我们可以看出, Executor 的功能和作⽤是:
 
(1 、根据传递的参数,完成 SQL 语句的动态解析,⽣成 BoundSql 对象,供 StatementHandler 使⽤;
(2 、为查询创建缓存,以提⾼性能
(3 、创建 JDBC Statement 连接对象,传递给 *StatementHandler* 对象,返回 List 查询结果

源码剖析-StatementHandler

StatementHandler 对象主要完成两个⼯作:
  • 对于JDBCPreparedStatement类型的对象,创建的过程中,我们使⽤的是SQL语句字符串会包 含若⼲个?占位符,我们其后再对占位符进⾏设值。StatementHandler通过 parameterize(statement)⽅法对 Statement 进⾏设值;
  • StatementHandler 通过 List query(Statement statement, ResultHandler resultHandler)⽅法来 完成执⾏Statement,和将Statement对象返回的resultSet封装成List

进⼊到 StatementHandler parameterize(statement)⽅法的实现:

 //参数化
  void parameterize(Statement statement)
      throws SQLException;

这里将参数进行替换设置

这个接口的实现是

PreparedStatementHandler 预处理语句处理器(PREPARED)
 @Override
  public void parameterize(Statement statement) throws SQLException {
    //调用ParameterHandler.setParameters
    parameterHandler.setParameters((PreparedStatement) statement);
  }

下面是设置参数的具体实现

//设置参数
  /** ParameterHandler 类的 setParameters(PreparedStatement ps) 实现
   * 对某⼀个Statement进⾏设置参数
   * */
  @Override
  public void setParameters(PreparedStatement ps) throws SQLException {
    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) {
          //如果不是OUT,才设进去
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            //若有额外的参数, 设为额外的参数
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            //若参数为null,直接设null
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            //若参数有相应的TypeHandler,直接设object
            value = parameterObject;
          } else {
            //除此以外,MetaObject.getValue反射取得值设进去
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // 每⼀个 Mapping都有⼀个 TypeHandler,根据 TypeHandler 来对preparedStatement 进⾏设置参数
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            //不同类型的set方法不同,所以委派给子类的setParameter方法
            jdbcType = configuration.getJdbcTypeForNull();
          }
          //设置参数
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        }
      }
    }
  }

从上述的代码可以看到 ,StatementHandler parameterize(Statement) ⽅法调⽤了
ParameterHandler setParameters(statement) ⽅法,
ParameterHandler setParameters(Statement ) ⽅法负责根据我们输⼊的参数,对 statement 对象的 ?占位符处进⾏赋值。
进⼊到 StatementHandler List query(Statement statement, ResultHandler resultHandler) ⽅法的
实现:
@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 1.调⽤preparedStatemnt。execute()⽅法,然后将resultSet交给ResultSetHandler处理
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //2.使⽤ ResultHandler 来处理 ResultSet
    return resultSetHandler.<E> handleResultSets(ps);
  }

也是PreparedStatementHandler实现的方法

从上述代码我们可以看出, StatementHandler List query(Statement statement, ResultHandler
resultHandler) ⽅法的实现,是调⽤了 ResultSetHandler handleResultSets(Statement) ⽅法。 ResultSetHandler handleResultSets(Statement) ⽅法会将 Statement 语句执⾏后⽣成的 resultSet 结果集转换成List 结果集合
这里是 DefaultResultSetHandler 默认的结果处理器
 @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    //多ResultSet的结果集合,每个ResultSet对应⼀个Object对象。
    // ⽽实际上,每 个 Object 是List<Object> 对象。
    //在不考虑存储过程的多ResultSet的情况,普通的查询,实际就⼀个ResultSet,
    // 也就是说,multipleResults最多就⼀个元素。
    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    //获得⾸个ResultSet对象,并封装成ResultSetWrapper对象
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    //获得ResultMap数组
    //在不考虑存储过程的多ResultSet的情况,普通的查询,实际就⼀个ResultSet,
    // 也就是说,resultMaps就⼀个元素。
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    //一般resultMaps里只有一个元素
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      //获得ResultMap对象
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //处理ResultSet,将结果添加到multipleResults中
      handleResultSet(rsw, resultMap, multipleResults, null);
      //获得下⼀个ResultSet对象,并封装成ResultSetWrapper对象
      rsw = getNextResultSet(stmt);
      //清理
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
//因为'mappedStatement.resultSets'只在存储过程中使⽤,本系列暂时不考虑,忽略即可
    String[] resultSets = mappedStatement.getResulSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
//如果是multipleResults单元素,则取⾸元素返回
    return collapseSingleResultList(multipleResults);
  }

Mapper代理⽅式:

  public static void main(String[] args) {
    //前三步都相同
    InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = factory.openSession();
    //这⾥不再调⽤SqlSession的api,⽽是获得了接⼝对象,调⽤接⼝中的⽅法。
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> list = mapper.getUserByName("tom");
  }

思考⼀个问题,通常的 Mapper 接⼝我们都没有实现的⽅法却可以使⽤,是为什么呢?
答案很简单动态 代理
开始之前介绍⼀下 MyBatis 初始化时对接⼝的处理:
MapperRegistry Configuration 中的⼀个属性, 它内部维护⼀个HashMap ⽤于存放 mapper 接⼝的⼯⼚类,每个接⼝对应⼀个⼯⼚类。 mappers 中可以配置接⼝的包路径,或者某个具体的接⼝类。

 

<mapper class="com.lagou.mapper.UserMapper"/>
 <package name="com.lagou.mapper"/>
</mappers>
当解析 mappers 标签时,它会判断解析到的是 mapper 配置⽂件时,会再将对应配置⽂件中的增删 改 查标签 封装成MappedStatement 对象,存⼊ mappedStatements 中。 ( 上⽂介绍了 )
当判断解析到接⼝时,会建此接⼝对应的MapperProxyFactory 对象,存⼊ HashMap 中,
key = 接⼝的字节码对象,
value = 此接⼝对应的MapperProxyFactory 对象

源码剖析-getmapper()

进⼊ sqlSession.getMapper(UserMapper.class )

 映射器注册机

DefaultSqlSession默认的sqlSession

@Override
  public <T> T getMapper(Class<T> type) {
    //最后会去调用MapperRegistry.getMapper
    return configuration.<T>getMapper(type, this);
  }

调用的是Configuration中的getMapper

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

最终调用的是MapperRegistry 中的 getMapper 

//返回代理类
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //从 MapperRegistry 中的 HashMap 中拿 MapperProxyFactory
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //通过动态代理⼯⼚⽣成示例
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

这里的newInstance 方法如下

MapperProxyFactory

  public T newInstance(SqlSession sqlSession) {
    //创建了 JDK动态代理的Handler类
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    //调⽤了重载⽅法
    return newInstance(mapperProxy);
  }

下面是调用的JDK的动态代理 

  protected T newInstance(MapperProxy<T> mapperProxy) {
    //用JDK自带的动态代理生成映射器
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
/ 构造,传⼊了 SqlSession ,说明每个 session 中的代理对象的不同的!
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;
  //构造,传⼊了 SqlSession,说明每个session中的代理对象的不同的!
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }
...省略部分源码,剩余的在下面

}
在动态代理返回了示例后,我们就可以直接调⽤ mapper 类中的⽅法了,
但代理对象调⽤⽅法,执⾏是在MapperProxy 中的 invoke ⽅法中
//如果是Object定义的⽅法,直接调⽤
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
    //并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //这里优化了,去缓存中找MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //重点在这:MapperMethod最终调⽤了执⾏的⽅法
    return mapperMethod.execute(sqlSession, args);
  }

MapperMethod最终调⽤了 mapperMethod.execute(sqlSession, args); 执⾏的⽅法

 

/**
 * 映射器代理,代理模式
 *
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
    //并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //这里优化了,去缓存中找MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //执行
    return mapperMethod.execute(sqlSession, args);
  }

  //去缓存中找MapperMethod
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      //找不到才去new
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

}
MapperMethod进⼊execute⽅法:

方法类别的判断方法,在构造方法中初始化

private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, method);
  }

//执行
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
    //判断mapper中的⽅法类型,最终调⽤的还是SqlSession中的⽅法 switch(command.getType()) {


    if (SqlCommandType.INSERT == command.getType()) {
      //转换参数
      Object param = method.convertArgsToSqlCommandParam(args);
      //执⾏INSERT操作
      // 转换 rowCount
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      //转换参数
      Object param = method.convertArgsToSqlCommandParam(args);
      // 转换 rowCount
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      //转换参数
      Object param = method.convertArgsToSqlCommandParam(args);
      // 转换 rowCount
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      //⽆返回,并且有ResultHandler⽅法参数,则将查询的结果,提交给 ResultHandler 进⾏处理
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        //如果有结果处理器
        executeWithResultHandler(sqlSession, args);
        result = null;
        //执⾏查询,返回列表
      } else if (method.returnsMany()) {
        //如果结果有多条记录
        result = executeForMany(sqlSession, args);
        //执⾏查询,返回Map
      } else if (method.returnsMap()) {
        //如果结果是map
        result = executeForMap(sqlSession, args);
      } else {
        //否则就是一条记录 查询单条
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    //返回结果为null,并且返回类型为基本类型,则抛出BindingException异常
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

  //这个方法对返回值的类型进行了一些检查,使得更安全
  private Object rowCountResult(int rowCount) {
    final Object result;
    if (method.returnsVoid()) {
      result = null;
    } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
      //如果返回值是大int或小int
      result = Integer.valueOf(rowCount);
    } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
      //如果返回值是大long或小long
      result = Long.valueOf(rowCount);
    } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
      //如果返回值是大boolean或小boolean
      result = Boolean.valueOf(rowCount > 0);
    } else {
      throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    }
    return result;
  }

下面是参数转换成sql 语句中的参数

    public Object convertArgsToSqlCommandParam(Object[] args) {
      final int paramCount = params.size();
      if (args == null || paramCount == 0) {
        //如果没参数
        return null;
      } else if (!hasNamedParameters && paramCount == 1) {
        //如果只有一个参数
        return args[params.keySet().iterator().next().intValue()];
      } else {
        //否则,返回一个ParamMap,修改参数名,参数名就是其位置
        final Map<String, Object> param = new ParamMap<Object>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : params.entrySet()) {
          //1.先加一个#{0},#{1},#{2}...参数
          param.put(entry.getValue(), args[entry.getKey().intValue()]);
          // issue #71, add param names as param1, param2...but ensure backward compatibility
          final String genericParamName = "param" + String.valueOf(i + 1);
          if (!param.containsKey(genericParamName)) {
            //2.再加一个#{param1},#{param2}...参数
            //你可以传递多个参数给一个映射器方法。如果你这样做了, 
            //默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等。
            //如果你想改变参数的名称(只在多参数情况下) ,那么你可以在参数上使用@Param(“paramName”)注解。 
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
      }
    }

 

 好了Mybatis执行流程就讲到这了。后面会对部分进细节行更加详细的讲述

如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智达教育‍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值