MyBatis学习(三):MyBatis原理

本文详细解析了MyBatis执行SQL的过程,从SqlSessionFactory的创建到SqlSession的使用,再到Executor的查询逻辑,深入探讨了MyBatis如何通过封装和动态代理调用JDBC执行SQL,以及查询结果的缓存机制。

阅读本文默认您对mybatis有一定的认识,所以在此一些概念性的东西就不贴了,以一个查询为例,复现MyBatis如何通过封装和动态代理调用底层JDBC代码,在此期间经历了什么。

需要注意的点:

  • 对于mybatis使用的查询方式和类型的实现类,这里使用的是SimpleXXXXXX,不同的实现类有不同的实现方式
  • 主要介绍使用sqlSession执行查询时,底层代码流转,使用mapper方法查询的放到动态代理中,因为底层也是使用的sqlSession。

一、MyBatis如何执行一条sql?

先来看看我们平时是怎么使用sqlSession来进行查询的。

1.sqlSession执行查询代码

public static void main(String[] args) {
    // 读取主配置文件
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    // 创建SqlSessionFactory工厂
		SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    // 获取sqlSession
		SqlSession sqlSession = factory.openSession();
		String name = "tom";
    // 执行查询
		List<User> list = sqlSession.selectList("com.demo.mapper.UserMapper.getUserByName",params);
}

2.初始化SqlSessionFactory

// 1.我们最初调用的build
public SqlSessionFactory build(InputStream inputStream) {
	//调用了重载方法
    return build(inputStream, null, null);
  }

// 2.调用的重载方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //  XMLConfigBuilder是专门解析mybatis的配置文件的类
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //这里又调用了一个重载方法。parser.parse()的返回值是Configuration对象
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } //省略部分代码
  }

// 最终调用此方法默认返回DefaultSqlSessionFactory,这个类只有一个Configuration 属性
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

Configuration对象:

  • 其实就相当于是mybatis-config.xml的对象映射。只要是在xml文件中定义的节点类型,在Configuration中都可以找到对应的属性;
  • parser.parse()  这一步我们可以理解为将配置文件加载到内存中并存储,看下源码:
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 1.解析<configuration></configuration>内部内容
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

    // 以下分别对应mybatis-config.xml文件中的各个节点配置
  private void parseConfiguration(XNode root) {
    try {
      Properties settings = settingsAsPropertiess(root.evalNode("settings"));
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

到此,我们就可以将xml文件中的配置采用对象的方式加以利用了。到此,SqlSessionFactory对象已经创建好了。

3.通过SqlSessionFactory获取SqlSession对象(一般与线程绑定)

来看openSession()方法,调用了openSessionFromDataSource

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

// 此处先声明了sqlSession对象所需的属性,然后返回新建对象
// 大多数是配置信息的再次赋值
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);
        // 离jdbc查询方法最近的一层封装
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
  }

这里边Executor需要注意一下,我们即将使用的是SimpleExecutor。至此,一个SqlSession已经创建完毕。

4.执行sqlSession.selectList(String statement)

// 外部调用的方法
// 我们从调用可以看到这里传入的是mapper接口全路径名+方法名
public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
}

//重载的方法
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 通过statement获取到了唯一的MappedStatement对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
  }

首先:我们没有传入sql语句,myBatis怎么知道该执行哪一个sql?

答:MyBatis是通过接口全路径名+方法名来进行识别的,通过这个值可以得到唯一的一个MappedStatement对象,

那么也就是说我们需要(在启动时,或者使用XXXXXXBuilder手动创建)读取全部的*.xml文件,将文件统统转化为MappedStatement,并存到一个key-value的集合中。最后将这个集合存放到Configuration对象的mappedStatements中。

MappedStatement对象其实就是用来表示*.xml文件中标签信息的:

  • <select/>;
  • <insert/>;
  • <update/>;
  • <delete/>等

5.调用executor.query(XXX)

最终会使用BaseExecutor类中的query方法:

  • 有缓存直接取缓存数据
  • 没有缓存调用具体实现类查询数据库
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 这里创建了一个缓存键,在正式的查询过程中,会将查询结果缓存起来
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}


// 调用重载
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.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
        // 是否可以从本地缓存汇总获取到需要查询的数据?通过缓存键去寻找
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        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
        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;
    // 类似于先将这个key占住的意思
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        // 这里调用的是SimpleExecutor的doQuery方法
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        // 移除key
      localCache.removeObject(key);
    }
    // 加入缓存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

6.SimpleExecutor实现JDBC查询数据库

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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 1.我们知道Connection信息在配置文件中是存在的,在此获取PreperedStatement
      stmt = prepareStatement(handler, ms.getStatementLog());
        // PreperedStatement执行结果处理
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
}

// 2.获取获取PreperedStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // JDBC所使用的Connection对象
    Connection connection = getConnection(statementLog);
    // 获取stmt
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
}

// 注:BaseStatementHandler类中的方法
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
        // 经过千辛万苦,终于获取到了statement对象,具体获取如下图。
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      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);
    }
}

// 最终还是按照JDBC的方式完成了statement对象的创建
protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
}

7.返回到SimpleExecutor的doQuery()方法(最后一步

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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
        // 1.调用下边代码
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
}

public class SimpleStatementHandler extends BaseStatementHandler {

 /**其他代码省略*/

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    // 执行常规JDBC操作
    statement.execute(sql);
    // 处理结果集并返回
    return resultSetHandler.<E>handleResultSets(statement);
  }

}

至此,一个查询流程就走完了。总结一下MyBatis的代码执行顺序

  1. 创建SqlSessionFactory对象:SqlSessionFactoryBuilder会通过流的形式读取mybatis-config.xml文件,将配置封装成Configuration对象中,然后利用Configuration对象创建DefaultSqlSessionFactory(只有一个Configuration属性)。
  2. 打开SqlSession:通过SqlSessionFactory初始化SqlSession配置,设置一些默认值,比如默认使用SimpleExecutor等等
  3. 执行sql语句:SqlSession对象执行增删改查,本质是调用Executor接口的实现类
    1. 这里通过传入的全路径名+方法名参数,找到对应的MappedStatement对象,执行相应操作。
  4. Executor的具体实现类,对JDBC进行了各自的封装,包括:
    • 缓存结果集,如果通过缓存键在缓存中查询到,就直接取缓存的结果,
    • 创建PreperedStatement对象,执行查询,
    • 对返回结果集进行处理。

初次阅读,有错误的请指正!!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值