从源码聊聊mybatis一次查询都经历了些什么

原文地址
mybatis是一种很是流行的ORM框架,能够经过一些灵活简单的配置,大大提高咱们操做数据库的效率,固然,我以为它如此受欢迎的缘由更主要的是,它的源码设计的很是简单。接下来咱们就来聊聊使用mybatis作一次数据库查询操做背后都经历了什么。java

首先咱们先上一段很是简单的代码,这是原始的JDBC方式的数据库操做。sql

// 1. 建立数据源
DataSource dataSource = getDataSource();
// 2. 建立数据库链接
try (Connection conn = dataSource.getConnection()) {
    try {
        conn.setAutoCommit(false);
        // 3. 建立Statement
        PreparedStatement stat = conn.prepareStatement("select * from std_addr where id=?");
        stat.setLong(1, 123456L);
        // 4. 执行Statement,获取结果集
        ResultSet resultSet = stat.executeQuery();
        // 5. 处理结果集,这一步每每是很是复杂的
        processResultSet(resultSet);
        // 6.1 成功提交,对于查询操做,步骤6是不须要的
        conn.commit();
    } catch (Throwable throwable) {
    	// 6.2 失败回滚
        conn.rollback();
    }
}
复制代码

下面这段是mybatis链接数据库以及作一样的查询操做的代码。数据库

DataSource dataSource = getDataSource();
TransactionFactory txFactory = new JdbcTransactionFactory();
Environment env = new Environment("test", txFactory, dataSource);
Configuration conf = new Configuration(env);
conf.setMapUnderscoreToCamelCase(true);
conf.addMapper(AddressMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(conf);
try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
    AddressMapper mapper = sqlSession.getMapper(AddressMapper.class);
    Address addr = mapper.getById(123456L);
}
复制代码

这是mybatis的Mapper,也很是简单缓存

@Mapper
public interface AddressMapper {
    String TABLE = "std_addr";

    @Select("select * from " + TABLE + " where id=#{id}")
    Address getById(long id);
}
复制代码

从上面的代码能够看出,经过mybatis查询数据库须要如下几个步骤:mybatis

  1. 准备运行环境Environment,即建立数据源和事务工厂
  2. 建立核心配置对象Configuration,此对象包含mybatis的配置信息(xml或者注解方式配置)
  3. 建立SqlSessionFactory,用于建立数据库会话SqlSession
  4. 建立SqlSession进行数据库操做

下面咱们从源码逐步分析mybatis在一次select查询中这几个步骤的详细状况。app

准备运行环境Environment

Environment有两个核心属性,dataSource和transactionFactory,下面是源码框架

public final class Environment {
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;
}
复制代码

其中,dataSource用来获取数据库链接,transactionFactory用来建立事务。
咱们详细看一下mybatis的JdbcTransactionFactory的源码,这里能够经过数据源或者数据库链接来建立JdbcTransaction。ui

public class JdbcTransactionFactory implements TransactionFactory {
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }

  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}
复制代码

我把JdbcTransaction的源码精简了一下,大概是这个样子的。这里实际上就是把JDBC的DataSource或者一个Connection托管给了mybatis的Transaction对象,由Transaction来管理事务的提交与回滚。this

public class JdbcTransaction implements Transaction {
  protected Connection connection;
  protected DataSource dataSource;
  protected TransactionIsolationLevel level;
  protected boolean autoCommmit;

  public Connection getConnection() throws SQLException {
    if (connection == null) {
      connection = dataSource.getConnection();
      if (level != null) {
        connection.setTransactionIsolation(level.getLevel());
      }
      if (connection.getAutoCommit() != autoCommmit) {
        connection.setAutoCommit(autoCommmit);
      }
    }
    return connection;
  }

  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      connection.commit();
    }
  }

  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      connection.rollback();
    }
  }
}
复制代码

到这里,运行环境Environment已经准备完毕,咱们能够从Environment中获取DataSource或者建立一个新的Transaction,从而建立一个数据库链接。spa

建立核心配置对象Configuration

Configuration类很是复杂,包含不少配置信息,咱们优先关注如下核心属性

public class Configuration {
  protected Environment environment;
  protected boolean cacheEnabled = true;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  // 保存着全部Mapper的动态代理对象
  protected final MapperRegistry mapperRegistry;
  // 保存着全部类型处理器,处理Java类型和JDBC类型的转换
  protected final TypeHandlerRegistry typeHandlerRegistry;
  // 保存配置的Statement信息,能够是XML或注解
  protected final Map<String, MappedStatement> mappedStatements;
  // 保存二级缓存信息
  protected final Map<String, Cache> caches;
  // 保存配置的ResultMap信息
  protected final Map<String, ResultMap> resultMaps;
}
复制代码
  1. 从SqlSessionFactory的build方法能够看出,mybatis提供了两种解析配置信息的方式,分别是XMLConfigBuilder和MapperAnnotationBuilder。解析配置的过程,其实就是填充上述Configuration核心属性的过程。
// 根据XML构建
InputStream xmlInputStream = Resources.getResourceAsStream("xxx.xml");
SqlSessionFactory xmlSqlSessionFactory = new SqlSessionFactoryBuilder().build(xmlInputStream);
// 根据注解构建
Configuration configuration = new Configuration(environment);
configuration.addMapper(AddressMapper.class);
SqlSessionFactory annoSqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
复制代码
  1. TypeHandlerRegistry处理Java类型和JDBC类型的映射关系,从TypeHandler的接口定义能够看出,主要是用来为PreparedStatement设置参数和从结果集中获取结果的
public interface TypeHandlerRegistry<T> {
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  T getResult(ResultSet rs, String columnName) throws SQLException;
  T getResult(ResultSet rs, int columnIndex) throws SQLException;
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
复制代码

总而言之,Configuration对象包含了mybatis的Statement、ResultMap、Cache等核心配置,这些配置信息是后续执行SQL操做的关键。

建立SqlSessionFactory

咱们提供new SqlSessionFactoryBuilder().build(conf)构建了一个DefaultSqlSessionFactory,这是默认的SqlSessionFactory

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}
复制代码

DefaultSqlSessionFactory的核心方法有两个,代码精简事后是下面这个样子的。其实都是一个套路,经过数据源或者链接建立一个事务(上面提到的TransactionFactory建立事务的两种方式),而后建立执行器Executor,最终组合成一个DefaultSqlSession,表明着一次数据库会话,至关于一个JDBC的链接周期。

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  final Environment environment = configuration.getEnvironment();
  final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  final Executor executor = configuration.newExecutor(tx, execType);
  return new DefaultSqlSession(configuration, executor, autoCommit);
}

private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
  boolean autoCommit;
  try {
    autoCommit = connection.getAutoCommit();
  } catch (SQLException e) {
    autoCommit = true;
  }
  final Environment environment = configuration.getEnvironment();
  final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  final Transaction tx = transactionFactory.newTransaction(connection);
  final Executor executor = configuration.newExecutor(tx, execType);
  return new DefaultSqlSession(configuration, executor, autoCommit);
}
复制代码

下面这段代码是Configuration对象建立执行器Executor的过程,默认的状况下会建立SimpleExecutor,而后在包装一层用于二级缓存的CachingExecutor,很明显Executor的设计是一个典型的装饰者模式。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}
复制代码

建立SqlSession进行数据库操做

进行一次数据库查询操做的步骤以下:

1. 经过DefaultSqlSessionFactory建立一个DefaultSqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
复制代码
2. 建立获取一个Mapper的代理对象
AddressMapper mapper = sqlSession.getMapper(AddressMapper.class);
复制代码

DefaultSqlSession的getMapper方法参数是咱们定义的Mapper接口的Class对象,最终是从Configuration对象的mapperRegistry注册表中获取这个Mapper的代理对象。
下面是MapperRegistry的getMapper方法的核心代码,可见这里是经过MapperProxyFactory建立代理

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  return mapperProxyFactory.newInstance(sqlSession);
}
复制代码

而后是MapperProxyFactory的newInstance方法,看上去是否是至关熟悉。很明显,这是一段JDK动态代理的代码,这里会返回Mapper接口的一个代理类实例。

public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
复制代码
3. 调用代理对象的查询方法
Address byId = mapper.getById(110114);
复制代码

这里其实是调用到Mapper对应的MapperProxy,下面是MapperProxy的invoke方法的一部分。可见,这里针对咱们调用的Mapper的抽象方法,建立了一个对应的代理方法MapperMethod。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}
复制代码

我精简了MapperMethod的execute方法的代码,以下所示。其实最终动态代理为咱们调用了SqlSession的select方法。

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
  case SELECT:
    Object param = method.convertArgsToSqlCommandParam(args);
    result = sqlSession.selectOne(command.getName(), param);
    break;
  }
  return result;
}
复制代码
4. 接下来的关注点在SqlSession

SqlSession的selectOne方法最终是调用的selectList,这个方法也很是简单,入参statement其实就是咱们定义的Mapper中被调用的方法的全名,本例中就是x.x.AddressMapper.getById,经过statement获取对应的MappedStatement,而后交由executor执行query操做。

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  MappedStatement ms = configuration.getMappedStatement(statement);
  return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
复制代码

前面咱们提到过默认的执行器是SimpleExecutor再装饰一层CachingExecutor,下面看看CachingExecutor的query代码,在这个方法以前会先根据SQL和参数等信息建立一个缓存的CacheKey。下面这段代码也很是明了,若是配置了Mapper级别的二级缓存(默认是没有配置的),则优先从缓存中获取,不然将调用被装饰者也就是SimpleExecutor(实际上是BaseExecutor)的query方法。

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  Cache cache = ms.getCache();
  // cache不为空,表示当前Mapper配置了二级缓存
  if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      List<E> list = (List<E>) tcm.getObject(cache, key);
      // 缓存未命中,查库
      if (list == null) {
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list);
      }
      return list;
    }
  }
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
复制代码

BaseExecutor的query方法的核心代码以下所示,这里有个一级缓存,是开启的,默认的做用域是SqlSession级别的。若是一级缓存未命中,则调用queryFromDatabase方法从数据库中查询。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> 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);
  }
  return list;
}
复制代码

而后将调用子类SimpleExecutor的doQuery方法,核心代码以下。

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  Configuration configuration = ms.getConfiguration();
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  stmt = prepareStatement(handler, ms.getStatementLog());
  return handler.<E>query(stmt, resultHandler);
}
复制代码

经过源码发现Configuration建立的是一个RoutingStatementHandler,而后根据MappedStatement的statementType属性建立一个具体的StatementHandler(三种STATEMENT、PREPARED或者CALLABLE)。终于出现了一些熟悉的东西了,这不就是JDBC的三种Statement吗。咱们选择其中的PreparedStatementHandler来看一看源码,这里就很清晰了,就是调用了JDBC的PreparedStatement的execute方法,而后将结果交由ResultHandler处理。

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  return resultSetHandler.<E> handleResultSets(ps);
}
复制代码

从上面doQuery的代码能够看出,执行的Statement是由prepareStatement方法建立的,能够看出这里是调用了StatementHandler的prepare方法建立Statement,其实是经过MappedStatement的SQL、参数等信息,建立了一个预编译的PrepareStatement。

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  stmt = handler.prepare(connection, transaction.getTimeout());
  handler.parameterize(stmt);
  return stmt;
}
复制代码

最终,这个PrepareStatement的执行结果ResultSet,会交由DefaultResultSetHandler来处理,而后根据配置中的类型、Results、返回值等信息,生成对应的实体对象。

到这里咱们就分析完了mybatis作一次查询操做所经历的所有流程。固然,这里面还有一些细节没有提到,好比说二级缓存、参数和结果集的解析等,这些具体的内容可能会在后续的mybatis源码解析文章中详细描述。

相关文章
相关标签/搜索