跳到主要内容
  1. 所有文章/
  2. 《Mybatis3源码深度解析》笔记/

Mybatis源码-Mybatis核心组件

·📄 4704 字·🍵 10 分钟

基本使用 #

@Test
public  void testMybatis () throws IOException {
    // 获取配置文件输入流
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    // 通过SqlSessionFactoryBuilder的build()方法创建SqlSessionFactory实例
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 调用openSession()方法创建SqlSession实例
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 获取UserMapper代理对象
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    // 执行Mapper方法,获取执行结果
    List<UserEntity> userList = userMapper.listAllUser();
     /*
    // 兼容Ibatis,通过Mapper Id执行SQL操作
    List<UserEntity> userList = sqlSession.selectList(
            "com.blog4java.mybatis.com.blog4java.mybatis.example.mapper.UserMapper.listAllUser");
    */
    System.out.println(JSON.toJSONString(userList));
}

配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
  <settings>
    <setting name="useGeneratedKeys" value="true"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <setting name="logImpl" value="LOG4J"/>
  </settings>
  <typeAliases>
    <typeAlias type="com.blog4java.mybatis.example.entity.UserEntity" alias="User"/>
  </typeAliases>
  <environments default="dev" >
    <environment id="dev">
      <transactionManager type="JDBC">
        <property name="" value="" />
      </transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="org.hsqldb.jdbcDriver" />
        <property name="url" value="jdbc:hsqldb:mem:mybatis" />
        <property name="username" value="sa" />
        <property name="password" value="" />
      </dataSource>
    </environment>
    <environment id="qa">
      <transactionManager type="JDBC">
        <property name="" value="" />
      </transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="org.hsqldb.jdbcDriver" />
        <property name="url" value="jdbc:hsqldb:mem:mybatis_qa" />
        <property name="username" value="admin" />
        <property name="password" value="admin" />
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <mapper resource="com/blog4java/mybatis/example/mapper/UserMapper.xml"/>
    <!--
    <mapper resource="file:///mybatis/com/blog4java/mybatis/example/mapper/UserMapper.xml"/>
    <mapper class="com.blog4java.mybatis.com.blog4java.mybatis.example.mapper.UserMapper"/>
    <package name="com.blog4java.mybatis.com.blog4java.mybatis.example.mapper"/>
    -->
  </mappers>
</configuration>

Configuration #

用于描述MyBatis的主配置信息,其他组件需要获取配置信息时,直接通过Configuration对象获取。除此之外,MyBatis 在应用启动时,将Mapper配置信息、类型别名、TypeHandler等注册到Configuration组件中,其他组件需要这些信息时,也可以从Configuration对象中获取。

  1. MyBatis框架启动时,会对所有的配置信息进行解析,然后将解析后的内容注册到Configuration对象的这些属性中。属性的值可以在settings中指定。

    属性及含义可以参考官方文档

  2. 作为容器存放TypeHandler(类型处理器)、TypeAlias(类型别名)、Mapper接口及Mapper SQL配置信息。这些信息在MyBatis框架启动时注册到Configuration组件中。

  3. 除此之外,Configuration组件还作为Executor、StatementHandler、ResultSetHandler、ParameterHandler组件的工厂类,用于创建这些组件的实例。Configuration类中提供了这些组件的工厂方法,这些工厂方法签名如下:

    这些工厂方法会根据MyBatis不同的配置创建对应的实现类。典型的工厂方法模式应用。

Executor #

SqISession是MyBatis提供的面向用户的API,表示和数据库交互时的会话对象,用于完成数据库的增删改查功能。SqlSession是Executor组件的外观,目的是对外提供易于理解和使用的数据库操作接口。

Executor是MyBatis的SQL执行器,MyBatis中对数据库所有的增删改查操作都是由Executor组件完成的。

这些Executor都继承至BaseExecutor,BaseExecutor中定义的方法的执行流程及通用的处理逻辑,具体的方法由子类来实现,是典型的模板方法模式的应用。

  • SimpleExecutor是基础的Executor,能够完成基本的增删改查操作,
  • ResueExecutor对JDBC中的Statement对象做了缓存,当执行相同的SQL语句时,直接从缓存中取出Statement对象进行复用,避免了频繁创建和销毁Statement对象,从而提升系统性能,这是享元思想的应用。
  • BatchExecutor则会对调用同一个Mapper执行的update、insert和delete操作,调用Statement对象的批量操作功能。

另外,我们知道MyBatis支持一级缓存和二级缓存,当MyBatis开启了二级缓存功能时,会使用CachingExecutor 对SimpleExecutor、ResueExecutor、BatchExecutor进行装饰,为查询操作增加二级缓存功能,这是装饰器模式的应用。

MappedStatement #

MappedStatement用于描述Mapper 中的 SQL配置信息,是对MapperXML配置文件中<select/update/delete/insert>等标签或者@Select/@Update等注解配置信息的封装。

不同标签的属性可以参考官方文档

StatementHandler #

StatementHandler 封装了对JDBCStatement对象的操作,比如为Statement对象设置参数,调用Statement接口提供的方法与数据库交互,等等。

public interface StatementHandler {

  /**
   * @description: 该方法用于创建JDBCStatement对象,并完成Statement对象的属性设置。
   **/
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  /**
   * @description: 该方法使用MyBatis中的ParameterHandler组件, 为PreparedStatement和CallableStatement参数占位符设置值。
   **/
  void parameterize(Statement statement)
      throws SQLException;

  /**
   * @description: 将SQL命令添加到批处量执行列表中。
   **/
  void batch(Statement statement)
      throws SQLException;

  /**
   * @description: 调用 Statement对象的 execute() 方法执行更新语句,例如UPDATE、INSERT、DELETE语句。
   **/
  int update(Statement statement)
      throws SQLException;

  /**
   * @description: 执行查询语句,并使用ResultSetHandler处理查询结果集。
   **/
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

  /**
   * @description: 带游标的查询,返回Cursor对象,能够通过Iterator动态地从数据库中加载数据,适用于查询数据量较大的情况,避免将所有数据加载到内存中。
   **/
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

  /**
   * @description: 获取Mapper中配置的SQL信息,BoundSql封装了动态SQL解析后的SQL文本和参数映射信息
   **/
  BoundSql getBoundSql();

  /**
   * @description: 获取ParameterHandler 实例。
   **/
  ParameterHandler getParameterHandler();

}

  • BaseStatementHandler是一个抽象类,封装了通用的处理逻辑及方法执行流程,具体方法的实现由子类完成,这里使用到了设计模式中的模板方法模式。
  • SimpleStatementHandler继承至BaseStatementHandler,封装了对JDBCStatement对象的操作。
  • PreparedStatementHandler封装了对JDBC PreparedStatement对象的操作。
  • CallableStatementHandler则封装了对JDBC CallableStatement对象的操作。
  • RoutingStatementHandler会根据Mapper配置中的 statementType属性(取值为 STATEMENT、PREPARED或CALLABLE)创建对应的StatementHandler实现。

TypeHandler #

TypeHandler是MyBatis中的类型处理器,用于处理Java类型与JDBC类型之间的映射。它的作用主要体现在:

  1. 根据Java 类型调用PreparedStatement或CallableStatement对象对应的setXXX()方法为Statement对象设置值。
  2. 根据Java类型调用ResultSet对象对应的getXXX()获取SQL执行结果。
public interface TypeHandler<T> {
  //为PreparedStatement对象设置参数
  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;
}

MyBatis中内置了很多TypeHandler,例如StringTypeHandler用于java.lang.String类型和JDBC中的 CHAR、VARCHAR、LONGVARCHAR、NCHAR、NVARCHAR、LONGVARCHAR等类型之间的转换。StringTypeHandler中的逻辑非常简单,代码如下:

public class StringTypeHandler extends BaseTypeHandler<String> {
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setString(i, parameter);
  }
  @Override
  public String getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getString(columnName);
  }
  @Override
  public String getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getString(columnIndex);
  }
  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getString(columnIndex);
  }
}

MyBatis通过TypeHandlerRegistry建立JDBC类型、Java类型与TypeHandler之间的映射关系,代码如下:

在TypeHandlerRegistry类的构造方法中,通过registerO方法注册所有的TypeHandler,代码如下:

ParameterHandler #

当MyBatis 框架使用的 Statement类型为CallableStatement和PreparedStatement时,ParameterHandler用于为Statement对象参数占位符设置值。

public interface ParameterHandler {
  // 该方法用于获取执行Mapper时传入的参数对象
  Object getParameterObject();
  // 该方法用于为JDBCPreparedStatement或者CallableStatement对象设置参数值。
  void setParameters(PreparedStatement ps)
      throws SQLException;

}

ParameterHandler 接口只有一个默认的实现类,即DefaultParameterHandler,我们重点关注setParameters()方法,代码如下:

@Override
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 typeHandler = parameterMapping.getTypeHandler();
        JdbcType jdbcType = parameterMapping.getJdbcType();
        if (value == null && jdbcType == null) {
          jdbcType = configuration.getJdbcTypeForNull();
        }
        try {
          // 调用TypeHandler的setParameter方法,为Statement对象参数占位符设置值
          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);
        }
      }
    }
  }
}

MyBatis通过ParameterMapping描述参数映射的信息。在DefaultParameterHandler类的setParameters()方法中,首先获取Mapper配置中的参数映射,然后对所有参数映射信息进行遍历,接着根据参数名称获取对应的参数值,调用对应的TypeHandler对象的setParameter()方法为Statement对象中的参数占位符设置值。

ResultHandler #

ResultSetHandler封装了对JDBC中的ResultSet对象操作,当执行SQL类型为SELECT语句时,ResultSetHandler用于将查询结果转换成Java对象。

public interface ResultSetHandler {
  // 获取Statement对象中的ResultSet对象,对ResultSet对象进行处理,返回包含结果实体的List对象。
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;
  // 将ResultSet对象包装成Cursor对象,对Cursor进行遍历时,能够动态地从数据库查询数据,避免一次性将所有数据加载到内存中。
  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
  // 处理存储过程调用结果。
  void handleOutputParameters(CallableStatement cs) throws SQLException;
}

ResultSetHandler接口只有一个默认的实现,即DefaultResultSetHandler。接下来我们重点关注DefaultResultSetHandler对handleResultSets()方法的实现,代码如下:

@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
  final List<Object> multipleResults = new ArrayList<Object>();
  int resultSetCount = 0;
  // 1、获取ResultSet对象,將ResultSet对象包装为ResultSetWrapper
  ResultSetWrapper rsw = getFirstResultSet(stmt);
  // 2、获取ResultMap信息,一般只有一个ResultMap
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  int resultMapCount = resultMaps.size();
  // 校验ResultMap,如果该ResultMap名称没有配置,则抛出异常
  validateResultMapsCount(rsw, resultMapCount);
  // 如果指定了多个ResultMap,则对每个ResultMap进行处理
  while (rsw != null && resultMapCount > resultSetCount) {
    ResultMap resultMap = resultMaps.get(resultSetCount);
    // 3、调用handleResultSet方法处理结果集
    handleResultSet(rsw, resultMap, multipleResults, null);
    // 获取下一个结果集对象,需要JDBC驱动支持多结果集
    rsw = getNextResultSet(stmt);
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }
  // 如果JDBC驱动支持多结果集,可以通过<select>标签resultSets属性指定多个ResultMap
  // 处理<select>标签resultSets属性,该属性一般情况不会指定
  String[] resultSets = mappedStatement.getResultSets();
  if (resultSets != null) {
    while (rsw != null && resultSetCount < resultSets.length) {
      ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
      if (parentMapping != null) {
        String nestedResultMapId = parentMapping.getNestedResultMapId();
        ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
        //调用handleResultSet方法处理结果集
        handleResultSet(rsw, resultMap, null, parentMapping);
      }
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
  }
  // 对multipleResults进行处理,如果只有一个结果集,则返回结果集中的元素,否则返回多个结果集
  return collapseSingleResultList(multipleResults);
}