MyBatis源码分析一:核心组件

2021-01-13 15:42:13 浏览数 (1)

一、整体介绍

前面一篇文章MyBatis3使用 讲解了MyBatis的基本使用,这篇介绍MyBatis的核心组件,让我们从整体上了解MyBatis的组成。

MyBatis核心类/接口如下:

Configuration(主配置类)

MappedStatement:描述Xml中Sql配置

SqlSession:用户使用最多的就是这个接口,用于执行Sql

Executor:Sql执行器,上面SqlSession就是调用Executor来完成Sql执行的 几个Handler:

StatementHandler:封装了JDBC Statement对象的操作

ParameterHandler:当Statement为PreparedStatement或CallableStatement时用于设置参数

ResultHandler:用于将JDBC的ResultSet转换为Java对象

TypeHandler:用于处理JDBC类型和Java类型的转换

二、Configuration

Configuration:描述主配置信息,对应前面讲的主配置文件,另外它还是其它组件的容器,像Mapper、TypeHandler等都会注册到这里。

在主配置文件中settings节点配置的一些属性在这里都有对应。

常用配置如下:

cacheEnabled:是否开启2级缓存,2级缓存基于Mapper。

lazyLoadingEnabled:延迟加载开关,主要是延迟加载一些关联对象,建议设为true。

multiResultSetEnabled:是否允许一语句返回多结果集,建议关闭。

defaultStatementTimeout:Statement的超时时间,建议开启为3-10秒。

defaultFetchSize:从数据库获取最大行数,建议设置1000以内,后台系统除外。

三、MappedStatement

描述一条Sql语句,如前面一篇文章举例的:

代码语言:javascript复制
<select id="listAllUser"  resultType="com.liujh.entity.UserEntity" >
        select
        <include refid="userFields"/>
        from user
    </select>

select标签常用属性如下:

id:唯一标识

parameterType:参数的Java类型

resultType:描述结果和Java类的映射关系

timeout:超时时间

以下为例子

代码语言:javascript复制
 <resultMap id="BaseResultMap" type="Post">
        <id column="id" property="id" jdbcType="INTEGER"/>
        <result column="title" property="title" jdbcType="VARCHAR"/>
        <result column="author" property="author" jdbcType="VARCHAR"/>
        <result column="create_time" property="createTime" jdbcType="BIGINT"/>
    </resultMap>

    <sql id="Table_Name">
        post1
    </sql>

    <select id="list" resultMap="BaseResultMap" parameterType="Post">
        select
        id,title,author,create_time
        from
        <include refid="Table_Name"/>
        <where>
            <if test="title !=null and title!=''">
                and title like CONCAT(#{title},'%')
            </if>
        </where>

        <if test="offset!=null and limit!=null and limit>0">
            limit #{offset},#{limit}
        </if>

表结构如下:

代码语言:javascript复制
CREATE TABLE `post1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `author` varchar(255) DEFAULT NULL,
  `create_time` bigint(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

四、SqlSession

这个是门面类,面向用户Api大部分接口是通过这个接口暴露出去的,我们看一段代码:

代码语言:javascript复制
 // 获取配置文件输入流
  InputStream inputStream = Resources.getResourceAsStream("META-INF/spring/mybatis-config.xml");
  // 通过SqlSessionFactoryBuilder的build()方法创建SqlSessionFactory实例
  SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

  //构造数据源
  BoneCPDataSource dataSource= new BoneCPDataSource();
  dataSource.setDriverClass(driverClass);
  dataSource.setJdbcUrl(jdbcUrl);
  dataSource.setUsername(userNmae);
  dataSource.setPassword(password);

  String id = "SqlSessionFactoryBean";
  TransactionFactory transactionFactory = new SpringManagedTransactionFactory();
  Environment newEnv = new Environment(id, transactionFactory, dataSource);

  sqlSessionFactory.getConfiguration().setEnvironment(newEnv);
  // 调用openSession()方法创建SqlSession实例
  SqlSession sqlSession = sqlSessionFactory.openSession();
  // 获取UserMapper代理对象
  UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  // 执行Mapper方法,获取执行结果
  List<UserEntity> userList = userMapper.listAllUser();

可以看到SqlSession由SqlSessionFactory打开,执行SQL先由SqlSession获取一个Mapper,参数为Mapper接口类,然后直接执行其方法就可以了,Mapper接口定义如下:

代码语言:javascript复制
@Repository
public interface UserMapper {
    List<UserEntity> listAllUser();
}

五、Executor

真正执行Sql的组件,我们用的最多的是SimpleExecutor,还有加入缓存的CachingExecutor,以下为使用代码:

代码语言:javascript复制
 public void testExecutor() throws IOException, SQLException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    Configuration configuration = sqlSession.getConfiguration();
    MappedStatement listAllUserStmt = configuration.getMappedStatement(
            "com.edward.mapper.UserMapper.listAllUser");
    Executor executor = configuration.newExecutor(
            new JdbcTransaction(sqlSession.getConnection()),
            ExecutorType.SIMPLE
    );
    List<UserEntity> userList = executor.query(listAllUserStmt,
            null,
            RowBounds.DEFAULT,
            Executor.NO_RESULT_HANDLER);
}

可以看到Executor由Configuration创建,query即查询方法需要一个MappedStatement参数(由Configuration.getMappedStatement获取)。

其中query方法定义在父类BaseExecutor中

代码语言:javascript复制
@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

更新对应的update方法,这里不详述。

六、几个Handler

这几个不在主流程上,代码不是很复杂,可以根据自己情况查阅,挑一个ResultSetHandler讲下。

ResultSetHandler用于将JDBC返回的ResultSet转化成Java对象,看下定义

代码语言:javascript复制
public interface ResultSetHandler {

  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

其中handleOutputParameters用于处理存储过程输出参数,一般互联网公司不建议用存储过程,所以就跳过这个,看下handleResultSets。

代码语言:javascript复制
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //处理单行
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount  ;
    }

其调用handleResultSet来处理单个结果集,最终又通过ResultHandler::handleResult来处理单字段:

代码语言:javascript复制
@Override
  public void handleResult(ResultContext<? extends V> context) {
    final V value = context.getResultObject();
    final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
    // TODO is that assignment always true?
    final K key = (K) mo.getValue(mapKey);
    mappedResults.put(key, value);
  }

0 人点赞