Mybatis源码解析《二》

导语

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

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

public class MybatisTest {

    @Test
    public 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模板进行一个绑定合成一句完整的SQL
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 最后执行SQL
      return 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);
    // 准备stmt
    stmt = 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 {
      // 实例化statement
      statement = instantiateStatement(connection);
      // 设置statement超时时间
      setStatementTimeout(statement, transactionTimeout);
      // 设置fetch的size
      setFetchSize(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 params
            value = 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赋给PreparedStatement
    PreparedStatement 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;
  }

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

发布了46 篇原创文章 · 获赞 11 · 访问量 7904
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 1024 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览