Mybatis 源码分析

Mybatis,一款优秀 ORM 框架,它的核心是将 Java 对象和数据库表映射起来,方便对数据进行增删改查操作。
阅读源码的重点关注以下几点。

  • 1、如何找到要执行的 SQL
  • 2、如何执行 SQL,并返回对应的数据
  • 3、查询缓存如何存取

包的划分

MyBatis 的包划分很清晰,职责明确,如下图。

20210223153601.png

使用到的设计模式

在 MyBatis 源码中使用到了多种设计模式,可以重点关注其使用方法。

  • Builder 模式
  • 工厂模式
  • 单例模式
  • 代理模式
  • 装饰器模式
  • 适配器模式

Demo 执行流程

依照官网的文档,通过 不使用 XML 构建 SqlSessionFactory 的方式来运行一个简单的 Demo。


// 1. 构建一个数据源
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/learn");
dataSource.setUser("root");
dataSource.setPassword("123123");

//2. 构建一个 Configuration,用来创建 SqlSessionFactory
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(StudentMapper.class);

//3. 创建 DefaultSqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

//4. 开启 session, 得到 DefaultSqlSession
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {

  //5. 获取 mapperProxy 代理
  StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

  //6. 通过 mapperProxy 查询
  Student student = studentMapper.selectStudent(1);
  System.out.println(student);
}

public interface StudentMapper {

    @Select("SELECT * FROM test_mybatis WHERE id = #{id}")
    Student selectStudent(int id);
}

执行关键步骤源码解析

1、构建 DefaultSqlSessionFactory

DefaultSqlSessionFactory 通过 SqlSessionFactoryBuilder 构建,需要数据源、事务管理工厂及 Mapper 组成的 Configuration 为入参。
configuration.addMapper(StudentMapper.class); 添加 mapper ,关键代码如下:

org.apache.ibatis.binding.MapperRegistry.addMapper()


knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();

读取 Mapper 类中的方法,拿到 @Select 、@Insert、@Update 等注解,并获取注解的值,即拿到 SQL 语句,构建到 sqlSource 对象中。

org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.parse()
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.parseStatement()


getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
      final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);
      final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
      final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options)x.getAnnotation()).orElse(null);
      final String mappedStatementId = type.getName() + "." + method.getName();
      ...
});

2、创建 DefaultSqlSession

通过 sqlSessionFactory.openSession 创建 SqlSession 对象。

org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource()


final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//得到 JdbcTransaction 对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 默认开启缓存 ,使用 CachingExecutor
final Executor executor = configuration.newExecutor(tx, execType);
// 返回 DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);

3、获取 Mapper 代理对象

通过 sqlSession.getMapper 获取代理类对象。

org.apache.ibatis.binding.MapperRegistry.getMapper()。

knownMappers 中的元素是在第一步 addMapper 时放进去的。


final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
    // 创建一个 MapperProxy 代理
    return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}

4、执行查询

通过代理对象 org.apache.ibatis.binding.MapperProxy@691a7f8f.invoke() 查询。

cachedInvoker(method).invoke(proxy, method, args, sqlSession)

org.apache.ibatis.binding.MapperMethod.execute()


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());
    }

org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne()

可以看出 selectOne 在查询出多于 1 条时会直接报异常。


 // Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
    return list.get(0);
} else if (list.size() > 1) {
    throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
    return null;
}

org.apache.ibatis.executor.CachingExecutor.query()


BoundSql boundSql = ms.getBoundSql(parameterObject);
// 缓存 key 的创建
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

缓存 key 的创建源码如下,可以看出 key 是由 environmentId + mappedStatementId + offset + limit + sql + paramValue 组成的。
其中 mappedStatementId 是 Mapper 的方法路径名称。


CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
    if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          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);
        }
        cacheKey.update(value);
    }
}
if (configuration.getEnvironment() != null) {
    // issue #176
    cacheKey.update(configuration.getEnvironment().getId());
}

org.apache.ibatis.executor.BaseExecutor.query()

localCache 即 MyBatis 默认的缓存,是由一个 hashMap 存储的。


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);
}

org.apache.ibatis.executor.BaseExecutor.queryFromDatabase()

查询数据库,并放入缓存。


List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
    localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
}
return list;

org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getRowValue()

将查询结构放到一个新生成的对象中,该对象类型即为 Mapper 中指定的对象类型。


final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 创建 SQL 返回类型的对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    boolean foundValues = this.useConstructorMappings;
    // 将 SQL 查询结果 set 到对象的属性中
    if (shouldApplyAutomaticMappings(resultMap, false)) {
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
    }
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
}

执行流程总结

  • 1、构建一个数据源
    • 创建一个 DataSource 对象,链接数据库信息存储在此对象
  • 2、构建一个 Configuration,用来创建 SqlSessionFactory
    • 创建一个 transactionFactory 事务工厂对象
    • 通过 DataSource、transactionFactory 组建 Environment 对象
    • 通过 environment 对象,创建 configuration
    • 向 configuration 中添加 mapper class
      • knownMappers.put(type, new MapperProxyFactory<>(type));
      • new MapperAnnotationBuilder(config, type).parse();
        • 获取 Mapper 接口中方法的注解,取到类型和具体的 SQL
  • 3、创建 DefaultSqlSessionFactory
    • 通过 SqlSessionFactoryBuilder 以 configuration 为入参构建 defaultSqlSessionFactory 对象
  • 4、 开启 session, 得到 DefaultSqlSession
    • 默认开启缓存 ,使用 CachingExecutor
  • 5、获取 mapperProxy 代理
    • configuration.getMapper(type, this);
    • mapperRegistry.getMapper(type, sqlSession);
    • (MapperProxyFactory) knownMappers.get(type).newInstance(sqlSession) 创建 proxy 实例
  • 6、通过 mapperProxy 查询
    • mapperProxy.invoke()
    • mapperMethod.execute(sqlSession, args);
    • sqlSession.selectOne(command.getName(), param);
    • this.selectList(statement, parameter);
    • 转给 CachingExecutor执行查询,executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    • 生成缓存 key, CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    • delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      • 优先取缓存 resultHandler == null ? (List) localCache.getObject(key) : null;
      • queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql)
        • doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
          • 将查询的结果字段值放入到新建对象中,defaultResultSetHandler.getRowValue()
        • 查询结果放入缓存, localCache.putObject(key, list);