SQL执行流程分析

在前面三篇文章中,我们介绍了Mybatis的基本配置文件和使用方法,在本文中将深入源码,介绍SQL语句的执行流程。

还是搬出我们前面的demo,这次聚焦main方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) throws Exception{
    String resource = "mybatis-config.xml";
    try {
        // 1. 获取 sqlSessionFactory
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
                .build(Resources.getResourceAsReader(resource));
        // 2. 获取 sqlSession
        SqlSession sqlSession = sessionFactory.openSession(true);
        // 3. 获取 mapper 接口
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 4. 执行 sql 语句
        List<UserDo> allUsers = userMapper.findAllUsers();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

从上面可以看到,要想执行一条sql语句的基本流程是:

  1. 解析配置文件,从中获取 sqlSessionFactory;
  2. 从 sqlSessionFactory 中获取 sqlSession;
  3. 从 sqlSession 中获取 mapper;
  4. 执行sql语句。

下面我们一步步来看。

SqlSessionFactory

从代码上来看,这个 SqlSessionFactory 是用建造者模式来完成实例化的,需要的参数是从Mybatis配置文件得到的Reader,来看一下build方法:

SqlSessionFactoryBuilder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 这里我们只传了 reader 一个参数
public SqlSessionFactory build(Reader reader) {
  return build(reader, null, null);
}
 
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
  try {
    // 实例化一个 XML的解析器,委托给它进行配置文件的解析
    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
    
    // parser.parse()就是具体的解析过程了,在前面介绍配置文件时有大概看过
    // 返回一个 Configuration 对象,继续点进去看
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      reader.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.
    }
  }
}

// 返回一个 DefaultSqlSessionFactory 实例对象
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

OK,到这里已经有一个 DefaultSqlSessionFactory 了。DefaultSqlSessionFactory 实现了 SqlSessionFactory 接口,我们来看一下这个接口都有哪些方法:

1
2
3
4
5
6
7
8
9
10
11
12
public interface SqlSessionFactory {
  SqlSession openSession();
  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();
}

一眼望去,全是开启 SqlSession 的方法,哦,还有一个获取配置类 Configuration 实例的方法。

接下来可以去获取 SqlSession 了。

SqlSession

在demo中,我们是这样获取sqlSession的: sessionFactory.openSession(true),参数true代表设置为自动提交事务,来看下源码:

DefaultSqlSessionFactory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public SqlSession openSession(boolean autoCommit) {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    // 这里的 configuration 就是上面实例化 DefaultSqlSessionFactory 传进来的
    // 里面包含了解析配置文件后得到的各种配置信息
    // Environment 对象包含了数据源和事务的配置
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    
    // Executor 是 sql 语句的实际执行者,封装了 statement
    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也拿到了,还是来看一下这个接口有啥方法吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public interface SqlSession extends Closeable {
  <T> T selectOne(String statement);
  <T> T selectOne(String statement, Object parameter);
  <E> List<E> selectList(String statement);
  <E> List<E> selectList(String statement, Object parameter);
  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
  <K, V> Map<K, V> selectMap(String statement, String mapKey);
  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
  <T> Cursor<T> selectCursor(String statement);
  <T> Cursor<T> selectCursor(String statement, Object parameter);
  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
  void select(String statement, Object parameter, ResultHandler handler);
  void select(String statement, ResultHandler handler);
  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
  int insert(String statement);
  int insert(String statement, Object parameter);
  int update(String statement);
  int update(String statement, Object parameter);
  int delete(String statement);
  int delete(String statement, Object parameter);

  void commit();
  void commit(boolean force);
  void rollback();
  void rollback(boolean force);

  List<BatchResult> flushStatements();

  @Override
  void close();

  void clearCache();

  Configuration getConfiguration();
  <T> T getMapper(Class<T> type);
  Connection getConnection();
}

大致看一下,主要是数据库的CRUD方法,以及事务的操作。

接下来该执行sql了。

MapperProxy

在demo中,我们是这样获取我们DAO——mapper的: sqlSession.getMapper(UserMapper.class)。要知道,我们只是写了DAO的接口文件,并没有它的实现类。 因此可以推测,是Mybatis替我们去做了这一步,进一步可以推测,这里可能存在着一个代理类。来看下源码验证下这个推测吧:

DefaultSqlSession.java

1
2
3
4
@Override
public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}

Configuration.java

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

MapperRegistry.java

1
2
3
4
5
6
7
8
9
10
11
12
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    // 在这里,要真正地去实例化一个mapper了
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

MapperProxyFactory.java

1
2
3
4
5
6
7
8
9
public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
  // 返回一个动态代理的对象
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

果不其然,通过代理的方式来实现DAO层mapper接口,进而执行sql语句。下面就看看具体是怎么执行的吧。

MapperProxy.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else if (method.isDefault()) {
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  // 往下看
  return mapperMethod.execute(sqlSession, args);
}

MapperMethod.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT:
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {
        result = executeForCursor(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
        if (method.returnsOptional()
            && (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements();
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  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;
}

看着老长,但仔细一看,具体的执行还是回到了 sqlSession。前面曾看了一下SqlSession接口里头的方法,现在既然又转回来了,那就挑一个看一下:

DefaultSqlSession.java

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    // 这里,甩给executor去做了
    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();
  }
}

然后在 BaseExecutor 类里一路向下: query() -> queryFromDatabase() -> doQuery()

doQuery() 方法在 BaseExecutor 的子类中实现,这里看一下 SimpleExecutor里的实现:

SimpleExecutor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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 中封装了 Statement
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 向下
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

最常用的是 PreparedStatementHandler ,就来看一下它的 query() 方法:

PreparedStatementHandler.java

1
2
3
4
5
6
7
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  // 回到了最原始的 JDBC 了
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  return resultSetHandler.handleResultSets(ps);
}

到这里,终于执行了一条sql语句了,让我们简单地回顾一下:

  1. 起始站:先是获得一个 mapper 的代理类(MapperProxy);
  2. 甩给 MapperMethod 去执行(mapperMethod.execute(...));
  3. 甩给 sqlSession 去执行(sqlSession.selectList(...));
  4. 甩给 executor 去执行(executor.query(...));
  5. 甩给 StatementHandler 去执行(handler.query(...));
  6. 终点站:甩给PreparedStatement去执行(ps.execute())。

–END–