MyBatis源码解析之核心组件

在此声明,此文章是对江荣波老师的《MyBatis3源码深度解析》的总结,尊重原作者。

MyBatis核心组件

在这里插入图片描述

这些组件的作用如下:

  • Configuration:用于描述MyBatis的主要配置信息,其他组件需要获取配置信息的时候,直接通过Configuration对象获取,MyBatis在应用启动的时候,将Mapper配置信息、类型别名、TypeHandler等注册到Configuration组件中,其他组件需要这些信息的时候,也可以从这个对象获取。
  • MappedStatement:MappedStatement用于描述Mapper中的SQL配置信息,是对Mapper XML配置文件中的<select|update|delete|insert>等标签或者@Select/@Update等注解配置信息的封装。
  • SqlSession:SqlSession是MyBatis提供的面向用户的API,表示和数据库的会话对象,用于完成对数据库的操作功能,SqlSssion是Executor组建的外观,目的是对外提供易于理解和使用的数据库操作对象。
  • Executor:它是MyBatis的SQL执行器,MyBatis所有对数据库的操作都是Executor完成的。
  • StatementHandler:StatementHandler封装了对JDBC Statement对象的操作,比如为Statement对象设置参数,调用Statement接口提供的方法与数据库交互,等等。
  • ParameterHandler:为Statement(PrepareStatement、CallableStatement)对象参数占位符设置值。
  • ResultSetHandler:封装了对JDBC中的ResultSet对象操作。当我们执行Slelect语句时,它负责帮我们把查询出来的数据转换成Java对象。
  • TypeHandler:类型处理器,用于处理JDBC类型和Java类型的映射关系,它的作用在于,我们在为PrepareStatement或者CallableStatement设置参数是,可以根据相应的Java类型找到对应的JDBC类型,而且在将查询结果转换成Java对象时,能根据JDBC类型匹配上Java数据类型。

配置信息类Configuration

MyBatis框架的配置信息有两种:

  1. 配置MyBatis框架属性的主配置文件,对应着Configuration类里面的属性,通过XPath解析XML文件得到相对应的配置,以下只是几个常用的属性,具体可参考官方文档。

    <settings>
    	<!-- 这个配置使全局的映射器启用或禁用二级缓存 -->
    	<setting name="cacheEnabled" value="true" />
    	<!-- 允许 JDBC 支持生成的键。需要适合的驱动。如果设置为 true 则这个设置强制生成的键被使用,
    	尽管一些驱动拒绝兼容但仍然有效(比如 Derby) -->
    	<setting name="useGeneratedKeys" value="true" />
    	<!-- 配置默认的执行器。SIMPLE 执行器没有什么特别之处。REUSE 执行器重用预处理语句。
    	  BATCH 执行器重用语句和批量更新 -->
    	<setting name="defaultExecutorType" value="REUSE" />
    	<!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 -->
    	<setting name="lazyLoadingEnabled" value="false" />
    	<!-- 设置超时时间,它决定驱动等待一个数据库响应的时间。 -->
    	<setting name="defaultStatementTimeout" value="25000" />
    	......
    </settings>	
    
  2. 配置执行SQL语句的Mapper配置文件,就像下面的例子。

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.blog4java.mybatis.example.mapper.UserMapper">
        <sql id="userAllField">
          id,create_time, name, password, phone, nick_name
        </sql>
    
        <select id="listAllUser"  resultType="com.blog4java.mybatis.example.entity.UserEntity" >
            select
            <include refid="userAllField"/>
            from user
        </select>
    
        <select id="getUserByEntity"  resultType="com.blog4java.mybatis.example.entity.UserEntity">
            select
            <include refid="userAllField"/>
            from user
            <where>
                <if test="id != null">
                    AND id = #{id}
                </if>
                <if test="name != null">
                    AND name = #{name}
                </if>
                <if test="phone != null">
                    AND phone = #{phone}
                </if>
            </where>
        </select>
    
        <select id="getUserByPhone" resultType="com.blog4java.mybatis.example.entity.UserEntity">
            select
            <include refid="userAllField"/>
            from user
            where phone = ${phone}
        </select>
    </mapper>
    
    

Configuration不仅作为一个配置类,还作为一个容器存放了TypeHandler、TypeAlias、Mapper接口以及Mapper SQL配置信息。MyBatis在启动的时候,会对所有的配置进行解析,然后将解析的内容注册到Configuration对应的属性中。
Configuration类中通过以下属性保存TypeHandler、TypeAlias等信息。

  //用于注册Mapper接口信息,建立Mapper接口的Class对象和MapperProxyFactory对象
  //(MapperProxyFactory用于创建Mapper的代理对象MapperProxy)
  //之间的联系,作为键值对注册到mapperRegistry的knownMappers属性中。这里用到了工厂模式。
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  //用于注册MyBatis插件信息,MyBatis插件实际就是一个拦截器,拦截器可以有多个,这里用到了责任链模式。
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  //用于注册所有的TypeHandler
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  //用于注册所有的类型别名
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  //用于注册LanguageDriver,LanguageDriver用于解析SQL配置,将配置信息转换为SqlSource对象。
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  //将所有的MappedStatement对象注册到该属性,key为Mapper的Id,Value为MappedStatement对象
  protected final Map<String, MappedStatement> mappedStatements = 
  		new StrictMap<MappedStatement>("Mapped Statements collection");
  //用于注册Mapper中配置的所有缓存信息,key为Cache的Id,也就是Mapper的命名空间,value为Cache对象。
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  //用于注册Mapper配置文件中通过<ResultMap>标签配置的ResultMap信息,
  //ResultMap用于建立Java实体属性与数据库字段之间的映射关系,
  //key为ResultMap的Id,该Id是由mapper的命名空间和<ResultMap>标签的id属性组成的,value为解析的ResultMap对象。
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  //用于注册Mapper中通过<ParamterMap>标签解析的参数映射信息,
  //key为ParamterMap的Id,该Id是由mapper的命名空间和<ResultMap>标签的id属性组成的,value为ParamterMap对象
  protected final Map<String, ParameterMap> parameterMaps = 
  		new StrictMap<ParameterMap>("Parameter Maps collection");
  //用于注册KeyGenerator,KeyGenerator是MyBatis的主键生成器。
  protected final Map<String, KeyGenerator> keyGenerators = 
  		new StrictMap<KeyGenerator>("Key Generators collection");
  //用于注册所以Mapper XML配置文件路径
  protected final Set<String> loadedResources = new HashSet<String>();
  //用于注册Mapper中通<sql>标签配置的SQL片段,key为这段SQL片段的id,value为XNode对象。
  protected final Map<String, XNode> sqlFragments = 
  		new StrictMap<XNode>("XML fragments parsed from previous mappers");

除此之外,Configuration组件还作为Executor、StatementHandler、ParameterHandler组件的工厂类,用于创建这些组件的实例:

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang()
    		.createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 执行拦截器链的拦截逻辑
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, 
  		RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = 
    	new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    // 执行拦截器链的拦截逻辑
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, 
  		Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = 
    	new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 执行拦截器链的拦截逻辑
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
   public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 根据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);
    }
    // 如果cacheEnabled属性为ture,这使用CachingExecutor对上面创建的Executor进行装饰
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 执行拦截器链的拦截逻辑
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

这些工厂方法会根据不同的配置创建对应的实现类,例如如果我们把defaultExecutorType属性设置为REUSE,那么newExecutor返回的是ReuseExecutor实例。设置为Simple,返回的就是SimpleExecutor实例。这就是典型的工厂方法模式的应用。

SQL配置信息MappedStatement

MyBatis3通过MappedStatement描述<select|update|delete|insert>等标签或者@Select/@Update等注解配置的SQL信息。

MappedStatement通过以下这些属性保存<select|update|delete|insert>标签的属性信息。

  //在命名空间的唯一标识符
  private String id;
  //指定SQL执行后返回的最大行数
  private Integer fetchSize;
  //等待数据库返回请求结果的秒数,超出抛出异常
  private Integer timeout;
  //Statement的类型,默认为PrepareStatement
  private StatementType statementType;
  //设置ResultSet对象的特征
  private ResultSetType resultSetType;
  //废弃
  private ParameterMap parameterMap;
  //<resultMap>标签配置的实体属性和数据库字段之间建立的结果集映射。
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  //是否使用二级缓存,默认为true
  private boolean useCache;
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;
  //指定LanguageDriver实现。
  private LanguageDriver lang;
  //适用于<update|insert>,用于将数据库自增主键的值返回填充到实体的属性中。
  private String[] keyProperties;
  private String[] keyColumns;
  private String databaseId;
  //这个设置仅对多结果集的情况适用。
  private String[] resultSets;

除此之外,MappedStatement类还有一些其他的属性。

  private Cache cache; // 二级缓存实例
  private SqlSource sqlSource; // 解析SQL语句生成的SqlSource实例
  private String resource; // Mapper资源路径
  private Configuration configuration; // Configuration对象的引用
  private KeyGenerator keyGenerator;  // 默认为Jdbc3KeyGenerator,即数据库自增主键,当配置了<selectKey>时,使用SelectKeyGenerator
  private boolean hasNestedResultMaps; // 是否有嵌套的ResultMap
  private Log statementLog; // 输出日志

SQL执行器Executor

Executor是真正执行SQL操作的组件,它定义了增速删改查的方法,Executor有以下几种不同的实现类。
在这里插入图片描述
这里用到的几种设计模式:

  • 模板方法模式:我们阅读源码可以发现,在BaseExecutor中定义的方法执行流程以及通用的处理逻辑,具体的方法由子类来实现。
  • 享元模式:ReuseExecutor对JDBC中的Statement对象做了缓存,当执行相同的SQL语句时,直接从缓存中提取出Statement对象进行复用,避免了频繁的创建和销毁Statement对象。
  • 装饰器模式:当MyBatis开启了二级缓存功能时,会使用CachingExecutor对BaseExecutor的子类进行装饰,为查询操作增加了二级缓存功能。

Executor和数据库交互需要Mapper配置信息,因此Executor需要一个MappedStatement对象作为参数。MyBatis在应用启动的时候,会解析所有的Mapper配置,并将他们注册到Configuration组件中,我们可以调用Configuration对象的getMappedStatement()方法获取对用的MappedStatement对象,之后根据SQL类型调用Executor对象的方法。

操作数据库的类StatementHandler

StatementHandler组件封装了对JDBC Statement的操作,例如设置Statement对象的fetchSize属性、设置查询超时时间的等。StatementHandler有以下几种不同的实现类。
在这里插入图片描述
这里用到的几种设计模式:

  • 模板方法模式:SimpleStatmentHandler继承自StatementHandler,封装了对JDBC Statement对象t的操作;PreparedStatementHandler封装了对JDBC PreparedStatement对象的操作;而CallableStatementHandler封装了对JDBC CallableStatement对象t的操作。
  • 工厂方法模式:RoutingStatementHandler会根据Mapper配置中的statementType属性创建对应的StatementHandler实例。

StatementHandler接口中定义了以下方法:

public interface StatementHandler {
  //该方法用于创建JDBC Statemen对象,并完成Statement对象的属性设置。
  Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
  //该方法使用ParameterHandler组件为Statement参数占位符设置值。
  void parameterize(Statement statement)  throws SQLException;
  //将SQL命令添加到批量处理执行列表中。
  void batch(Statement statement) throws SQLException;
  //调用Statement对象的execute()方法执行更新(包含UPDATE,INSERT,DELETE)语句。
  int update(Statement statement) throws SQLException;
  //执行查询语句,并使用ResultSetHabdler处理查询的结果集。
  <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
  //带游标的查询,返回Cursor对象,能够通过迭代动态的从数据库中加载数据,适用于查询数据量较大的情况,避免将所有的数据加载到内存。
  <E> Cursor<E> queryCursor(Statement statement) throws SQLException;
  //获取Mapper中配置的SQL信息,BondSQL封装了动态SQL解析后的SQL文本和参数映射信息。
  BoundSql getBoundSql();
  //获取ParameterHandler实例。
  ParameterHandler getParameterHandler();

}

参数设置类ParameterHandler

当使用PreparedStatement或者CallableStatement对象时,如果SQL语句中有参数占位符,在执行SQL语句之前,就需要为参数占位符设置值。ParameterHandler的作用是在PreparedStatementHandler和CallableStatementHandler操作对应Statement执行数据库交互之前为参数占位符设置值。ParameterHandler只有一个实现类,即DefaultParameterHandler。
在这里插入图片描述

ParameterHandler接口只有两个方法:

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

查询结果转换类ResultSetHandler

ResultSetHandler用于在StatementHandler对象执行完查询操作或存储过程之后,对结果集或存储过程的执行结果进行处理。ResultSetHandler只有一个实现类,那就是DefaultResultSetHandler。
在这里插入图片描述
ResultSetHandler接口定义如下:

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

类型处理器TypeHandler

TypeHandler是为了解决JDBC类型和Java类型之间的转换而产生的一个组件。BaseTypeHandler实现了TypeHandler,对调用setParamter()方法,参数为Null的情况做了通用的处理。对调用getResult()方法,从ResultSet对象中获取列值出现的异常作了处理。因此我们需要自定义TypeHandler时,只需要继承BaseTypeHandler就行。
在这里插入图片描述
TypeHandler定义的接口如下:

public interface TypeHandler<T> {
  //为PreparedStatement或者CallableStatement对象设置参数
  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通过TypeHandlerRegistry建立JDBC类型、Java类型与TypeHandler之间的映射关系。通过Map保存JDBC类型、Java类型与TypeHandler之间的关系,在TypeHandlerRegistry的构造方法中,通过regist()方法注册所有的TypeHandler。

  public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
    ......
  }

当我们自定义TypeHandler后,也可以调用TypeHandlerRegistry的regist()方法进行注册。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值