Mybatis源码分析

一、mybatis执行大概流程

在这里插入图片描述

  1. 会话(SqlSession):SqlSession 是myBatis的门面(采用门面模式设计),核心作用是为用户提供API。API包括增、删、改、查以及提交、关闭等。其自身是没有能力处理这些请求的,所以内部会包含一个唯一的执行器 Executor,所有请求都会交给执行器来处理。
  2. 执行器(Executor):Executor是一个大管家,核心功能包括:缓存维护、获取动态SQL、获取连接、以及最终的JDBC调用等。Executor内部包含了若干个组件:(1)缓存维护 :cache(2)获取连接:Transaction,(3)获取动态sql:SqlSource,(4)调用jdbc:StatementHandler
  3. SQL处理器(StatementHandler):在JDBC中执行一次sql的步骤包括。预编译SQL、设置参数然后执行。StatementHandler就是用来处理这三步。同样它也需要两个助手分别是:(1)设置参数 ParameterHandler,(2)读取结果 ResultSetHandler

涉及到主要的类

类名说明
MapperProxy用于实现动态代理,是InvocationHandler接口的实现类。
MapperMethod主要作用是将我们定义的接口方法转换成MappedStatement对象。
DefaultSqlSession默认会话
CachingExecutor二级缓存执行器(这里没有用到)
BaseExecutor抽像类,基础执行器,包括一级缓存逻辑在此实现
SimpleExecutor可以理解成默认执行器
JdbcTransaction事物管理器,会话当中的连接由它负责
PooledDataSourcemyBatis自带的默认连接池数据源
UnpooledDataSource用于一次性获取连接的数据源
StatementHandlerSQL执行处理器
RoutingStatementHandler用于根据 MappedStatement 的执行类型确定使用哪种处理器:如STATEMENT(单次执行)、PREPARED(预处理)、CALLABLE(存储过程)
BaseStatementHandlerStatementHandler基础类
PreparedStatementHandlerSql预处理执行器
ConnectionLogger用于记录Connection对像的方法调用日志。
DefaultParameterHandler默认预处理器实现
BaseTypeHandlerjava类型与JDBC类型映射处理基础类
IntegerTypeHandlerInteger与JDBC类型映射处理
PreparedStatementLogger用于记录PreparedStatement对像方法调用日志。

二、Executor执行器

在这里插入图片描述

  1. 简单执行器:SimpleExecutor默认执行器,它的行为是每处理一次会话当中的SQL请求都会通过对应的StatementHandler 构建一个新个Statement,这就会导致即使是相同SQL语句也无法重用Statement,所以就有了(ReuseExecutor)可重用执行器
  2. 可重用执行器:ReuseExecutor 区别在于他会将在会话期间内的Statement进行缓存,并使用SQL语句作为Key。所以当执行下一请求的时候,不在重复构建Statement,而是从缓存中取出并设置参数,然后执行。
  3. 批处理执行器:​ BatchExecutor 顾名思议,它就是用来作批处理的。但会将所 有SQL请求集中起来,最后调用Executor.flushStatements() 方法时一次性将所有请求发送至数据库。只有连续相同的SQL语句并且相同的SQL映射声明,才会重用Statement,并利用其批处理功能。否则会构建一个新的Satement然后在flushStatements() 时一次执行。这么做的原因是它要保证执行顺序。跟调用顺序一至
  4. 基础执行器:BaseExecutor 基础执行器主要是用于维护缓存和事物。事物是通过会话中调用commit、rollback进行管理。重点在于缓存这块它是如何处理的? (这里的缓存是指一级缓存),它实现了Executor中的Query与update方法。会话中SQL请求,正是调用的这两个方法。Query方法中处理一级缓存逻辑,即根据SQL及参数判断缓存中是否存在数据,有就走缓存。否则就会调用子类的doQuery() 方法去查询数据库,然后在设置缓存。在doUpdate() 中主要是用于清空缓存。
  5. 缓存执行器:CachingExecutor,这是用于处理二级缓存的。为什么不把它和一级缓存一起处理呢?因为二级缓存和一级缓存相对独立的逻辑,而且二级缓存可以通过参数控制关闭,而一级缓存是不可以的。综上原因把二级缓存单独抽出来处理。抽取的方式采用了装饰者设计模式,即在CachingExecutor 对原有的执行器进行包装,处理完二级缓存逻辑之后,把SQL执行相关的逻辑交给实至的Executor处理。

2.1 一级缓存

一级缓存:也叫做会话级缓存,生命周期仅存在于当前会话,不可以直接关关闭。但可以通过flushCache和localCacheScope对其做相应控制。
二级缓存:也叫应用级性缓存,缓存对象存在于整个应用周期,而且可以跨线程使用。

2.1.1 一级缓存的命中场景

缓存命中参数:

  1. SQL与参数相同:
  2. 同一个会话:
  3. 相同的MapperStatement ID:
  4. RowBounds行范围相同:

触发清空缓存

  1. 手动调用clearCache
  2. 执行提交回滚
  3. 执行update
  4. 配置flushCache=true
  5. 缓存作用域为Statement

在这里插入图片描述

2.1.2 一级缓存源码解析

一级缓存逻辑就存在于 BaseExecutor (基础执行器)里面。当会话接收到查询请求之后,会交给执行器的doQuery方法,在这里会通过 Sql、参数、分页条件等参数创建一个缓存key,在基于这个key去 PerpetualCache中查找对应的缓存值,如果有主直接返回。没有就会查询数据库,然后在填充缓存。
在这里插入图片描述
BaseExecutor 中的代码

  @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);	// 通过这个key获取一级缓存的数据
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }
  
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // queryStack 主要是用来嵌套处理的,一、前置清空,即配置了flushCache=true
    if (queryStack == 0 && ms.isFlushCacheRequired()) {	
      clearLocalCache();	// 清空一级缓存
    }
    List<E> list;
    try {
      queryStack++;
      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);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {		// 处理对象之间的循环依赖
        deferredLoad.load();
      }
      deferredLoads.clear();
      // 二、后置清空,配置了缓存作用域为statement查询结束合会清空缓存。 
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache();
      }
    }
    return list;
  }

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    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;
  }

以SimpleExecutor为例

  @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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);	//SQL处理器,
      stmt = prepareStatement(handler, ms.getStatementLog());	// 参数处理
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

PreparedStatementHandler,后面会将

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);	//结果处理器 DefaultResultSetHandler
  }

2.1.3 一级缓存的清空

缓存的清空对应BaseExecutor中的 clearLocalCache.方法。只要找到调用该方法地方,就知道哪些场景中会清空缓存了。

  1. update: 执行任意增删改
  2. select:查询又分为两种情况清空,一、前置清空,即配置了flushCache=true。二、后置清空,配置了缓存作用域为statement查询结束合会清空缓存。
  3. commit:提交前清空
  4. Rolback:回滚前清空
  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
  @Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

2.2 二级缓存

二级缓存也称作是应用级缓存,与一级缓存不同的,是它的作用范围是整个应用,而且可以跨线程使用。所以二级缓存有更高的命中率,适合缓存一些修改较少的数据。**在流程上是先访问二级缓存,在访问一级缓存。

2.2.1 二缓存需求

二级缓存是一个完整的缓存解决方案,那应该包含哪些功能呢?这里我们分为核心功能和非核心功能两类:

存储【核心功能】

即缓存数据库存储在哪里?常用的方案如下:

  1. 内存:最简单就是在内存当中,不仅实现简单,而且速度快。内存弊端就是不能持久化,且容易有限。
  2. 硬盘:可以持久化,容量大。但访问速度不如内存,一般会结合内存一起使用。
  3. 第三方集成:在分布式情况,如果想和其它节点共享缓存,只能第三方软件进行集成。比如Redis.
溢出淘汰【核心功能】

无论哪种存储都必须有一个容易,当容量满的时候就要进行清除,清除的算法即溢出淘汰机制。常见算法如下:

  1. FIFO:先进先出
  2. LRU:最近最少使用
  3. WeakReference: 弱引用,将缓存对象进行弱引用包装,当Java进行gc的时候,不论当前的内存空间是否足够,这个对象都会被回收
  4. SoftReference:软件引用,基机与弱引用类似,不同在于只有当空间不足时GC才才回收软引用对象。
其它功能
  1. 过期清理:指清理存放数据过久的数据
  2. 线程安全:保证缓存可以被多个线程同时使用
  3. 写安全:当拿到缓存数据后,可对其进行修改,而不影响原本的缓存数据。通常采取做法是对缓存对象进行深拷贝。
    在这里插入图片描述

2.2.2二级缓存责任链设计

这么多的功能,如何才能简单的实现,并保证它的灵活性与扩展性呢?这里MyBatis抽像出Cache接口,其只定义了缓存中最基本的功能方法:

  1. 设置缓存
  2. 获取缓存
  3. 清除缓存
  4. 获取缓存数量

然后上述中每一个功能都会对应一个组件类,并基于装饰者加责任链的模式,将各个组件进行串联。在执行缓存的基本功能时,其它的缓存逻辑会沿着这个责任链依次往下传递。
在这里插入图片描述

这样设计有以下优点:

  1. 职责单一:各个节点只负责自己的逻辑,不需要关心其它节点。
  2. 扩展性强:可根据需要扩展节点、删除节点,还可以调换顺序保证灵活性。
  3. 松耦合:各节点之间不没强制依赖其它节点。而是通过顶层的Cache接口进行间接依赖。

2.2.3 二级缓存的使用

缓存空间声明

二级默认缓存默认是不开启的,需要为其声明缓存空间才可以使用,通过 @CacheNamespace 或 为指定的 MappedStatement 声明。声明之后该缓存为该 Mapper 所独有,其它 Mapper 不能访问。如需要多个Mapper共享一个缓存空间可通过@CacheNamespaceRef 或进行引用同一个缓存空间。@CacheNamespace 详细配置见下表:

配置说明
implementation指定缓存的存储实现类,默认是用HashMap存储在内存当中
eviction指定缓存溢出淘汰实现类,默认LRU ,清除最少使用
flushInterval设置缓存定时全部清空时间,默认不清空
size指定缓存容量,超出后就会按eviction指定算法进行淘汰
readWritetrue即通过序列化复制,来保证缓存对象是可读写的,默认true
blocking为每个Key的访问添加阻塞锁,防止缓存击穿
properties为上述组件,配置额外参数,key对应组件中的字段名
缓存其它配置

除@CacheNamespace 还可以通过其它参数来控制二缓存

字段配置域说明
cacheEnabled二级缓存全局开关,默认开启
useCache<select|update|insert|delete>指定的statement是否开启,默认开启
flushCache<select|update|insert|delete>执行sql前是否清空当前二级缓存空间,update默认true。query默认false
二级缓存的命中条件

二级缓存的命中场景与一级缓存类似,不同在于二级可以跨会放使用,还有就是二级缓存的更新,必须是在会话提交之后。在这里插入图片描述

为什么要提交之后才能命中缓存?

在这里插入图片描述
如上图两个会话在修改同一数据,当会话二修改后,在将其查询出来,假如它实时填充到二级缓存,而会话一就能过缓存获取修改之后的数据,但实质是修改的数据回滚了,并没真正的提交到数据库。
所以为了保证数据一至性,二级缓存必须是会话提交之才会真正填充,包括对缓存的清空,也必须是会话正常提交之后才生效。

二级缓存结构

为了实现会话提交之后才变更二级缓存,MyBatis为每个会话设立了若干个暂存区,当前会话对指定缓存空间的变更,都存放在对应的暂存区,当会话提交之后才会提交到每个暂存区对应的缓存空间。为了统一管理这些暂存区,每个会话都一个唯一的事物缓存管理 器。所以这里暂存区也可叫做事物缓存。

在这里插入图片描述
在这里插入图片描述

二级缓存执行流程

原本会话是通过Executor实现SQL调用,这里基于装饰器模式使用CachingExecutor对SQL调用逻辑进行拦截。以嵌入二级缓存相关逻辑。
在这里插入图片描述

查询操作query

当会话调用query() 时,会基于查询语句、参数等数据组成缓存Key,然后尝试从二级缓存中读取数据。读到就直接返回,没有就调用被装饰的Executor去查询数据库,然后在填充至对应的暂存区。

请注意,这里的查询是实时从缓存空间读取的,而变更,只会记录在暂存区

更新操作update

当执行update操作时,同样会基于查询的语句和参数组成缓存KEY,然后在执行update之前清空缓存。这里清空只针对暂存区,同时记录清空的标记,以便当会话提交之时,依据该标记去清空二级缓存空间。

如果在查询操作中配置了flushCache=true ,也会执行相同的操作。

提交操作commit

当会话执行commit操作后,会将该会话下所有暂存区的变更,更新到对应二级缓存空间去。

2.2.4 二级缓存代码详解

CachingExecutor

public class CachingExecutor implements Executor {

  private final Executor delegate;	// 指向下一个执行器(BaseExecutor)
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();	// 缓存管理器

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();	// 是否开启了二级缓存
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);	// 获取二级缓存
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // 存入暂存区
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {
      tcm.clear(cache);		// 清空暂存区
    }
  }

TransactionalCacheManager (缓存管理器)

public class TransactionalCacheManager {
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }
  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }
  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }
  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }
  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }
  private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }
}

TransactionalCache

public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

  private final Cache delegate;
  private boolean clearOnCommit;	//是否在commit时清除二级缓存的标记
  private final Map<Object, Object> entriesToAddOnCommit;	//暂存区,
  private final Set<Object> entriesMissedInCache;	// // 缓存未命中的数据,事务commit时,也会放入二级缓存(key,null)

  @Override
  public Object getObject(Object key) {
    Object object = delegate.getObject(key);	//获取二级缓存
    if (object == null) {
      entriesMissedInCache.add(key);	// 记录未命中的key
    }
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);	// 存至暂存区
  }

  public void commit() {
    if (clearOnCommit) {
      delegate.clear();	// 清空二级缓存
    }
    flushPendingEntries();
    reset();
  }

  private void flushPendingEntries() {
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());	// 存入二级缓存
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);	
      }
    }
  }

三、Jdbc处理器StatementHandler解析

MyBatis一个基于JDBC的Dao框架,MyBatis把所有跟JDBC相关的操作全部都放到了StatementHandler中
在这里插入图片描述
JDBC处理器,基于JDBC构建JDBC Statement,并设置参数,然后执行Sql。每调用会话当中一次SQl,都会有与之相对应的且唯一的Statement实例。

3.1 StatementHandler结构

​ StatementHandler接口定义了JDBC操作的相关方法如下:

// 基于JDBC 声明Statement
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
// 为Statement 设置方法
void parameterize(Statement statement) throws SQLException;
// 添加批处理(并非执行)
void batch(Statement statement) throws SQLException;
// 执行update操作
int update(Statement statement) throws SQLException;
// 执行query操作
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;

在这里插入图片描述

3.2 处理流程解析

在这里插入图片描述
总共执行过程分为三个阶段:

  1. 预处理:这里预处理不仅仅是通过Connection创建Statement,还包括设置参数。
  2. 执行:包含执行SQL和处理结果映射两部分。
  3. 关闭:直接关闭Statement。

参数处理和结果集封装,涉及数据库字段和JavaBean之间的相互映射,相对复杂。所以分别使用ParameterHandler与ResultSetHandler两个专门的组件实现。

3.3 参数处理

参数处理即将Java Bean转换成数据类型。总共要经历过三个步骤,参数转换、参数映射、参数赋值。

3.3.1 参数转换

在这里插入图片描述
即将JAVA 方法中的普通参数,封装转换成Map,以便map中的key和sql的参数引用相对应。

@Select({"select * from users where name=#{name} or age=#{user.age}"})
@Options
User selectByNameOrAge(@Param("name") String name, @Param("user") User user);
  • 单个参数的情况下且没有设置@param注解会直接转换,勿略SQL中的引用名称。
  • 多个参数情况:优先采用@Param中设置的名称,如果没有则用参数序号代替 即"param1、parm2…"
  • 如果javac编译时设置了 -parameters 编译参数,也可以直接获取源码中的变量名称作为key

以上所有转换逻辑均在ParamNameResolver中实现。

3.3.2 参数映射

映射是指Map中的key如何与SQL中绑定的参数相对应。以下这几种情况

  • 单个原始类型:直接映射,勿略SQL中引用名称
  • Map类型:基于Map key映射
  • Object:基于属性名称映射,支持嵌套对象属性访问

在Object类型中,支持通过“.”方式映射属中的属性。如:user.age

3.3.3 参数赋值

通过TypeHandler 为PrepareStatement设置值,通常情况下一般的数据类型MyBatis都有与之相对应的TypeHandler

3.3.4 结果集封装

指读取ResultSet数据,并将每一行转换成相对应的对象。用户可在转换的过程当中可以通过ResultContext来控制是否要继续转换。转换后的对象都会暂存在ResultHandler中最后统一封装成list返回给调用方
在这里插入图片描述

结果集转换中99%的逻辑DefaultResultSetHandler 中实现。整个流程可大致分为以下阶段:

  1. 读取结果集
  2. 遍历结果集当中的行
  3. 创建对象
  4. 填充属性

3.4 代码详解

SimpleExecutor

  @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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());	//声明Statement 
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());	//声明Statement 
    handler.parameterize(stmt);		//设置参数
    return stmt;
  }

BaseStatementHandler

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);		// 创建Statement 
      setStatementTimeout(statement, transactionTimeout);	// 设置超时
      setFetchSize(statement);		// 设置查询数量
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

PreparedStatementHandler

  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
  	// 设置参数,交给DefaultParameterHandler
    parameterHandler.setParameters((PreparedStatement) statement);
  }

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();	// 执行SQL
    return resultSetHandler.handleResultSets(ps);	//结果处理,交给DefaultResultSetHandler
  }

DefaultParameterHandler(难点)(对应流程图在3.3.1)

  @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;
          // 参数映射的名字,在ParamNameResolver中的getNamedParams映射好了
          String propertyName = parameterMapping.getProperty();	
          if (boundSql.hasAdditionalParameter(propertyName)) {	// 与注解@Param相对应
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {	//只有一个参数
            value = parameterObject;
          } else {	// 多个参数。是Map类型
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // TypeHandler 类型处理器,mybatis内置了很多,如BooleanTypeHandler、IntegerTypeHandler等等
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();	//参数的数据类型
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
          	// 设置参数数据,每一种数据类型对应的类型处理器来进行处理,如ps.setInt(i, parameter);
            typeHandler.setParameter(ps, i + 1, value, jdbcType);	
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

ParamNameResolver(参数转换)

  //即将JAVA 方法中的普通参数,封装转换成Map,以便map中的key和sql的参数引用相对应。
  public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

在这里插入图片描述

DefaultResultSetHandler(难点)(对应流程图在3.3.4)(太复杂了,不做详细解析)

  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

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

    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(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

四、映射

4.1 映射工具MetaObject

所谓映射是指结果集中的列填充至JAVA Bean属性。这就必须用到反射,而Bean的属性 多种多样的有普通属性、对象、集合、Map都有可能。为了更加方便的操作Bean的属性,MyBatis提供了MeataObject 工具类,其简化了对象属性的操作。其具体功能如下:

  1. 查找属性:勿略大小写,支持驼峰、支持子属性 如:“blog.comment.user_name”
  2. 获取属性
    1. 基于点获取子属性 “user.name”
    2. 基于索引获取列表值 “users[1].id”
    3. 基于key获取map值 “user[name]”
  3. 设置属性:
    1. 可设置子属性值
    2. 支持自动创建子属性(必须带有空参构造方法,且不能是集合)

MetaObject 相继依赖了BeanWrapper、MetaClass、Reflector。这四个对象关系如下:
在这里插入图片描述

  • BeanWrapper: 功能与MeataObject类似,不同点是BeanWrapper只针对单个当前对象属性进行操作,不能操作子属性。
  • MetaClass :类的反射功能支持,获能获取整完整类的属性,包括属性的属性。
  • Reflector :类的反射功能支持,仅支持当前类的属性。

4.1.1 Meata获取属性流程:

“comments[0].user.name”
在这里插入图片描述

MetaObjbt 解析获取流程如下图:
在这里插入图片描述

MeataObject.getValue()
获取值的入品,首先根据属性名"comments[0].user.name" 解析成PropertyTokenizer,并基于属性中的“.”来判断是否为子属性值,如果是就递归调用getValue()获取子属性对象。然后在递归调用getValue()获取子属性下的属性。直到最后的name属性获。

MeataObject.setValue() 流程与getValue()类似,不同在于如果子属性不存在,则会尝试创建子属性。

4.1.2 代码详解

MetaObject

public class MetaObject {

  private final Object originalObject;
  private final ObjectWrapper objectWrapper;
  private final ObjectFactory objectFactory;
  private final ObjectWrapperFactory objectWrapperFactory;
  private final ReflectorFactory reflectorFactory;

  private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;
	// ObjectWrapper 里面会包含有 MetaClass
    if (object instanceof ObjectWrapper) {
      this.objectWrapper = (ObjectWrapper) object;	
    } else if (objectWrapperFactory.hasWrapperFor(object)) {
      this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } else if (object instanceof Map) {
      this.objectWrapper = new MapWrapper(this, (Map) object);
    } else if (object instanceof Collection) {
      this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } else {
      this.objectWrapper = new BeanWrapper(this, object);
    }
  }
	
  public Object getValue(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
        return null;
      } else {
        return metaValue.getValue(prop.getChildren());
      }
    } else {
      return objectWrapper.get(prop);
    }
  }

  public void setValue(String name, Object value) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
        if (value == null) {
          // don't instantiate child path if value is null
          return;
        } else {
          metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
        }
      }
      metaValue.setValue(prop.getChildren(), value);
    } else {
      objectWrapper.set(prop, value);
    }
  }

  public MetaObject metaObjectForProperty(String name) {
    Object value = getValue(name);
    return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
  }

PropertyTokenizer (这设计真不错,点个赞)

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
  private String name;
  private final String indexedName;		//保存了全名,如a[]
  private String index;
  private final String children;

  public PropertyTokenizer(String fullname) {
    int delim = fullname.indexOf('.');
    if (delim > -1) {
      name = fullname.substring(0, delim);
      children = fullname.substring(delim + 1);
    } else {
      name = fullname;
      children = null;
    }
    indexedName = name;
    delim = name.indexOf('[');
    if (delim > -1) {
      index = name.substring(delim + 1, name.length() - 1);
      name = name.substring(0, delim);
    }
  }

以BeanWrapper为例(在objectWrapper.get(prop)调用时)

public class BeanWrapper extends BaseWrapper {
  private final Object object;
  private final MetaClass metaClass;

  @Override
  public Object get(PropertyTokenizer prop) {
    if (prop.getIndex() != null) {	// 处理数组
      Object collection = resolveCollection(prop, object);
      return getCollectionValue(prop, collection);
    } else {
      return getBeanProperty(prop, object);	// 最后会调用反射来获取value
    }
  }
  @Override
  public void set(PropertyTokenizer prop, Object value) {
    if (prop.getIndex() != null) {
      Object collection = resolveCollection(prop, object);
      setCollectionValue(prop, collection, value);
    } else {
      setBeanProperty(prop, object, value);
    }
  }

4.2 ResultMap结果集映射

映射是指返回的ResultSet列与Java Bean 属性之间的对应关系。通过ResultMapping进行映射描述,在用ResultMap封装成一个整体。
在这里插入图片描述

4.2.1 手动映射设置

一个ResultMap 中包含多个ResultMapping 表示一个具体的JAVA属性到列的映射,其主要值如下:
在这里插入图片描述
ResultMapping 有多种表现形式如下:

  1. constructor:构建参数字段
  2. id:ID字段
  3. result:普通结构集字段
  4. association:1对1关联字段
  5. Collection:1对多集合关联字段

在这里插入图片描述

4.2.2 自动映射

当前列名和属性名相同的情况下,可使用自动映射
在这里插入图片描述
自动映射条件

  1. 列名和属性名同时存在(勿略大小写)
  2. 当前列未手动设置映射
  3. 属性类别存在TypeHandler
  4. 开启autoMapping (默认开启)

4.3 嵌套子查询

但很多时候对象结构, 是树级程现的。即对象中包含对象。可以通过子查询获取子对象属性。
在这里插入图片描述
当依次解析Blog中的属性时,会先解析填充普通属性,当解析到复合对象时,就会触发对子查询。
在这里插入图片描述

五、懒加载 和 嵌套查询

5.1 懒加载

懒加载是为改善,解析对象属性时大量的嵌套子查询的并发问题。设置懒加载后,只有在使用指定属性时才会加载,从而分散SQL请求。

<resultMap id="blogMap" type="blog" autoMapping="true">
    <id column="id" property="id"></id>
    <association property="comments" column="id" select="selectCommentsByBlog" fetchType="lazy"/>
</resultMap>

在嵌套子查询中指定 fetchType=“lazy” 即可设置懒加载。在调用getComments时才会真正加载。此外调用:“equals”, “clone”, “hashCode”, “toString” 均会触发当前对象所有未执行的懒加载。通过设置全局参数aggressiveLazyLoading=true ,也可指定调用对象任意方法触发所有懒加载。

参数描述
lazyLoadingEnabled全局懒加载开关 默认false
aggressiveLazyLoading任意方法触发加载 默认false
fetchType加载方式 eager实时 lazy懒加载。默认eager

5.1.1 set覆盖 &序列化

当调用setXXX方法手动设置属性之后,对应的属性懒加载将会被移除,不会覆盖手动设置的值。

5.1.2原理

通过对Bean的动态代理,重写所有属性的getXxx方法。在获取属性前先判断属性是否加载?然后加载之。
在这里插入图片描述
在这里插入图片描述
代理之后Bean会包含一个MethodHandler,内部在包含一个Map用于存放待执行懒加载,执行前懒加载前会移除。LoadPair用于针对反序列化的Bean准备执行环境。ResultLoader用于执行加载操作,执行前如果原执行器关闭会创建一个新的。

特定属性如果加载失败,不会在进行二次加载。

5.1.3 Bean代理过程

代理过程发生在结果集解析 交创建对象之后(DefaultResultSetHandler.createResultObject),如果对应的属性设置了懒加载,则会通过ProxyFactory 创建代理对象,该对象继承自原对象,然后将对象的值全部拷贝到代理对像。并设置相应MethodHandler(原对象直接抛弃)

在这里插入图片描述

5.1.4 代码详解

DefaultResultSetHandler

  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    this.useConstructorMappings = false; // reset previous mapping result
    final List<Class<?>> constructorArgTypes = new ArrayList<>();
    final List<Object> constructorArgs = new ArrayList<>();
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
      for (ResultMapping propertyMapping : propertyMappings) {
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
          // 创建动态代理(JavassistProxyFactory.createProxy  -> EnhancedResultObjectProxyImpl)
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
          break;
        }
      }
    }
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
    return resultObject;
  }

EnhancedResultObjectProxyImpl

	// 使用了动态代理,调用时会触发这个方法
    @Override
    public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
      final String methodName = method.getName();
      try {
        synchronized (lazyLoader) {
          if (WRITE_REPLACE_METHOD.equals(methodName)) {	// writeReplace ,有关系列化的知识
            Object original;
            if (constructorArgTypes.isEmpty()) {
              original = objectFactory.create(type);
            } else {
              original = objectFactory.create(type, constructorArgTypes, constructorArgs);
            }
            PropertyCopier.copyBeanProperties(type, enhanced, original);
            if (lazyLoader.size() > 0) {
              return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
            } else {
              return original;
            }
          } else {	// 一般会进入到这里
            if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
            	// aggressive:任意方法触发加载 默认false。
            	// lazyLoadTriggerMethods:默认"equals", "clone", "hashCode", "toString"会触发懒加载
            	// ResultLoaderMap lazyLoader
              if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                lazyLoader.loadAll();	// 加载全部
              } else if (PropertyNamer.isSetter(methodName)) {	// 调用set方法
                final String property = PropertyNamer.methodToProperty(methodName);
                lazyLoader.remove(property);
              } else if (PropertyNamer.isGetter(methodName)) {	// 调用get方法
                final String property = PropertyNamer.methodToProperty(methodName);
                if (lazyLoader.hasLoader(property)) {
                  lazyLoader.load(property);
                }
              }
            }
          }
        }
        return methodProxy.invoke(enhanced, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }

ResultLoaderMap

  public void loadAll() throws SQLException {
    final Set<String> methodNameSet = loaderMap.keySet();
    String[] methodNames = methodNameSet.toArray(new String[methodNameSet.size()]);
    for (String methodName : methodNames) {
      load(methodName);
    }
  }
  
  public boolean load(String property) throws SQLException {
  	// 懒加载只会触发一次,不管成功是否失败
    LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
    if (pair != null) {
      pair.load();
      return true;
    }
    return false;
  }

ResultLoaderMap.LoadPair

	private transient MetaObject metaResultObject;
    private transient ResultLoader resultLoader;
    private final transient Object serializationCheck = new Object();
    
    public void load() throws SQLException {
      if (this.metaResultObject == null) {
        throw new IllegalArgumentException("metaResultObject is null");
      }
      if (this.resultLoader == null) {
        throw new IllegalArgumentException("resultLoader is null");
      }
      this.load(null);
    }

    public void load(final Object userObject) throws SQLException {
      if (this.metaResultObject == null || this.resultLoader == null) {
        if (this.mappedParameter == null) {
          throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
                  + "required parameter of mapped statement ["
                  + this.mappedStatement + "] is not serializable.");
        }

        final Configuration config = this.getConfiguration();
        final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
        if (ms == null) {
          throw new ExecutorException("Cannot lazy load property [" + this.property
                  + "] of deserialized object [" + userObject.getClass()
                  + "] because configuration does not contain statement ["
                  + this.mappedStatement + "]");
        }

        this.metaResultObject = config.newMetaObject(userObject);
        this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
                metaResultObject.getSetterType(this.property), null, null);
      }
      // 判断是否为反序列化,但是永远不会被执行,上面已经判断了是否反序列化
      if (this.serializationCheck == null) {
        final ResultLoader old = this.resultLoader;
        this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
                old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
      }
      // resultLoader.loadResult()
      this.metaResultObject.setValue(property, this.resultLoader.loadResult());
    }

ResultLoader

  public Object loadResult() throws SQLException {
    List<Object> list = selectList();
    resultObject = resultExtractor.extractObjectFromList(list, targetType);
    return resultObject;
  }

  private <E> List<E> selectList() throws SQLException {
    Executor localExecutor = executor;
    // 跨线程使用,是否关闭
    if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
      localExecutor = newExecutor();
    }
    try {
      // 调用查询方法
      return localExecutor.query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
    } finally {
      if (localExecutor != executor) {
        localExecutor.close(false);
      }
    }
  }

  private Executor newExecutor() {
    final Environment environment = configuration.getEnvironment();
    if (environment == null) {
      throw new ExecutorException("ResultLoader could not load lazily.  Environment was not configured.");
    }
    final DataSource ds = environment.getDataSource();
    if (ds == null) {
      throw new ExecutorException("ResultLoader could not load lazily.  DataSource was not configured.");
    }
    final TransactionFactory transactionFactory = environment.getTransactionFactory();
    final Transaction tx = transactionFactory.newTransaction(ds, null, false);
    return configuration.newExecutor(tx, ExecutorType.SIMPLE);
  }

5.2 联合查询 和 嵌套映射

映射是指返回的ResultSet列与Java Bean 属性之间的对应关系。通过ResultMapping进行映射描述,在用ResultMap封装成一个整体。映射分为简单映射与复合嵌套映射。

简单映射:即返回的结果集列与对象属性是1对1的关系,这种情况下ResultHandler 会依次遍历结果集中的行,并给每一行创建一个对象,然后在遍历结果集列填充至对象的映射属性。

在这里插入图片描述
嵌套映射:但很多时候对象结构, 是树级程现的。即对象中包含对象。与之对应映射也是这种嵌套结构。
在这里插入图片描述
在配置方式上可以直接配置子映射,也以引入外部映射和自动映射。共有两类嵌套结构分别是一对多 与多对多 。
在这里插入图片描述

5.2.1 联合查询

有了映射之后如何获取结果?普通的单表查询是无法获取复合映射所需结果,这就必须用到联合查询。然后在将联合查询返回的数据列,拆分给不同的对象属性。1对1与1对多拆分和创建的方式是一样的。

1对1查询映射
select a.id,
       a.title,
       b.id as user_id,
       b.name as user_name
from blog a
         left join users b on a.author_id=b.id
where a.id = 1;

通过上述语句联合查询语句,可以得出下表中结果。结果中前两字段对应Blog,后两个字段对应User。然后在将User作为author属性填充至Blog对象。
在这里插入图片描述

1对多查询
select a.id,a.title,
       c.id as comment_id,
       c.body as comment_body
from blog a
         left join comment c on a.id=c.blog_id
where a.id = 1;

上述语句可得出三条结果,前两个字段对应Blog,后两个字段对应Comment(评论)。与1对1不同的是,三行指向的是同一Blog。因为它ID都是一样的。
在这里插入图片描述
上述结果中,相同的三行Blog将会创建一个Blog,同时分别创建三个不同的Comment组成一个集合,并填充至comments对象。

RowKey创建机制

在1对多的查询过程中,是基于RowKey来断定两行数据是否相同的 。RowKey一般基于。但有时并不会指定 这时将会采用其它映射字段创建RowKey具体规则如下:
在这里插入图片描述

结果集解析流程

在这里插入图片描述

在这里插入图片描述

流程说明:

所有映射流程的解析都是在DefaultResultSetHandler当中完成。主要方法如下:

1. handleRowValuesForNestedResultMap()

嵌套结果集解析入口,在这里会遍历结果集中所有行。并为每一行创建一个RowKey对象。然后调用getRowValue()获取解析结果对象。最后保存至ResultHandler中。

注:调用getRowValue前会基于RowKey获取已解析的对象,然后作为partialObject参数发给getRowValue

2. getRowValue()

该方法最终会基于当前行生成一个解析好对象。具体职责包括,1.创建对象、2.填充普通属性和3.填充嵌套属性。在解析嵌套属性时会以递归的方式在调用getRowValue获取子对象。最后一步4.基于RowKey 暂存当前解析对象。

3. applyNestedResultMappings()
解析并填充嵌套结果集映射,遍历所有嵌套映射,然后获取其嵌套ResultMap。接着创建RowKey 去获取暂存区的值。然后调用getRowValue 获取属性对象。最后填充至父对象。

​ 如果通过RowKey能获取到属性对象,它还是会去调用getRowsValue,因为有可能属下还存在未解析的属性。

六、动态SQL

在这里插入图片描述

<select id="findBlog"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE
  <if test="state != null">
   AND state = #{state}
  </if>
</select>

可以通过OGNL表达式来判断条件是否成立

6.1 先介绍下OGNL表达示

OGNL全称是对象导航图语言(Object Graph Navigation Language)是一种JAVA表达示语言,可以方便的存取对象属和方法,已用于逻辑判断。其支持以下特性:

  1. 获取属性属性值,以及子属性值进行逻辑计算

    id!=null||autho.name!=null

  2. 表达示中可直接调用方法,(如果是无参方法,可以省略括号)

    !comments.isEmpty&&comments.get(0)!=null

  3. 通过下标访问数组或集合

    comments[0].id!=null

  4. 遍历集合

    Iterable<?> comments = evaluator.evaluateIterable(“comments”, blog);

6.2 动态SQL脚本

前面所说动态SQL xml元素最终都会被解成一个可执行的脚本。而MyBatis 正是通过为这个脚本传递参数,并执行脚本计算来生成动态SQL。脚本在MyBatis中体现即SqlNode 。

在这里插入图片描述
每个动态元素都会有一个与之对应的脚本类。如if 对应ifSqlNode、forEarch对应ForEachSqlNode 以此类推下去。这里要注意下面三个脚本

  • StaticTextSqlNode 表示一段纯静态文本如: select * from user
  • TextSqlNode 表示一个通过参数拼装的文本如:select * from ${user}
  • MixedSqlNode 表示多个节点的集合
    在这里插入图片描述

6.3 动态脚本结构

脚本之间是呈现嵌套关系的。比如if元素中会包含一个MixedSqlNode ,而MixedSqlNode下又会包含1至1至多个其它节点。最后组成一课脚本语法树。如下面左边的SQL元素组成右边的语法树。在节点最底层一定是一个StaticTextNode或 TextNode
在这里插入图片描述

6.4 动态脚本执行

SqlNode的接口非常简单,就只有一个apply方法,方法的作用就是执行当前脚本节点逻辑,并把结果应用到DynamicContext当中去。

public interface SqlNode {
  boolean apply(DynamicContext context);
}

如IfSqlNode当中执行 apply时先计算If逻辑,如果通过就会继续去访问它的子节点。直到最后访问到TextNode 时把SQL文本添加至 DynamicContext。 通过这种类似递归方式Context就会访问到所有的的节点,并把最后最终符合条件的的SQL文本追加到 Context中。

//IfSqlNode
public boolean apply(DynamicContext context) {//计算if表达示
  if (evaluator.evaluateBoolean(test, context.getBindings())) {
    contents.apply(context);
    return true;
  }
  return false;
}
//StaticTextSqlNode
public boolean apply(DynamicContext context) {
  context.appendSql(text);
  return true;
}

在这里插入图片描述
访问完所有节点之后,就会生成一个SQL字符串,但这个并不是可直接执行的SQL,因为里面的参数还是表达式的形式#{name=name} 就需要通过SqlSourceBuilder 来构建可执行的SQL和参数映射ParameterMapping 。然后才能生成BoundSql。下图表示了在上下文中执行所有节点之后,最生成BoundSql。
在这里插入图片描述
看源码从动态SQL到BoundSql 过程中,中间还经过了一次StaticSqlSource 生成?为什么要这么做呢,以及从XML中解析出的SqlNode集存储在哪?这里又要有一个新的概念SqlSource SQL源。

6.5 SqlSource(SQL数据源)

在上层定义上每个Sql映射(MappedStatement)中都会包含一个SqlSource 用来获取可执行Sql(BoundSql)。SqlSource又分为原生SQL源与动态SQL源,以及第三方源。其关系如下图:
在这里插入图片描述

  • ProviderSqlSource :第三方法SQL源,每次获取SQL都会基于参数动态创建静态数据源,然后在创建BoundSql
  • DynamicSqlSource:动态SQL源包含了SQL脚本,每次获取SQL都会基于参数又及脚本,动态创建创建BoundSql
  • RawSqlSource:不包含任何动态元素,原生文本的SQL。但这个SQL是不能直接执行的,需要转换成BoundSql
  • StaticSqlSource:包含可执行的SQL,以及参数映射,可直接生成BoundSql。前面三个数据源都要先创建StaticSqlSource然后才创建BoundSql。

6.6 SqlSource解析过程

SqlSource 是基于XML解析而来,解析的底层是使用Dom4j 把XML解析成一个个子节点,在通过 XMLScriptBuilder 遍历这些子节点最后生成对应的Sql源。其解析流程如下图:
在这里插入图片描述
从图中可以看出这是一种递归式的访问 所有节点,如果是文本节点就会直接创建TextNode 或StaticSqlNode。否则就会创建动态脚本节点如IfSqlNode等。这里每种动态节点都会对应的处理器(NodeHandler)来创建。创建好之后又会继续访问子节点,让递归继续下去。当然子节点所创建的SqNode 也会作为当前所创建的元素的子节点而存在。

在这里插入图片描述

6.7 代码详解

CachingExecutor

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);	//获取BoundSql,从这里进去
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

MappedStatement

  public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);		//通过sqlSource获取BoundSql
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

DynamicSqlSource(动态编译)

public class DynamicSqlSource implements SqlSource {
  private final Configuration configuration;
  private final SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);		// 节点遍历
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());		//将#{}替换成?
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }
}

SqlSourceBuilder

  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

StaticSqlSource(最终返回StaticSqlSource)

public class StaticSqlSource implements SqlSource {
  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Configuration configuration;

  public StaticSqlSource(Configuration configuration, String sql) {
    this(configuration, sql, null);
  }

  public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.configuration = configuration;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }
}

七、Configuration配置体系

Configuration 是整个MyBatis的配置体系集中管理中心,前面所学Executor、StatementHandler、Cache、MappedStatement…等绝大部分组件都是由它直接或间接的创建和管理。此外影响这些组件行为的属性配置也是由它进行保存和维护。如cacheEnabled、lazyLoadingEnabled … 等。所以说它MyBatis的大管家很形象。

7.1、核心作用总结

  • 存储全局配置信息,其来源于settings(设置)

  • 初始化并维护全局基础组件

    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • plugins(插件)
    • environments(环境配置)
    • cache(二级缓存空间)
  • 初始化并维护MappedStatement

  • 组件构造器,并基于插件进行增强

    • newExecutor(执行器)
    • newStatementHandler(JDBC处理器)
    • newResultSetHandler(结果集处理器)
    • newParameterHandler(参数处理器)

7.2 配置来源

Configuration 配置来源有三项:

  1. Mybatis-config.xml 启动文件,全局配置、全局组件都是来源于此。
  2. Mapper.xml SQL映射(MappedStatement) \结果集映射(ResultMapper)都来源于此。
  3. @Annotation SQL映射与结果集映射的另一种表达形式。
    在这里插入图片描述

7.3 配置元素

Configuration 配置信息来源于xml和注解,每个文件和注解都是由若干个配置元素组成,并呈现嵌套关系,总体关系如下图所示:
在这里插入图片描述

关于各配置的使用请参见官网给出文档:https://mybatis.org/mybatis-3/zh/configuration.html#properties

7.4 元素承载

无论是xml 还是我注解这些配置元素最弱都要被转换成JAVA配置属性或对象组件来承载。其对应关系如下:

  1. 全配置(config.xml) 由Configuration对像属性承载
  2. sql映射<select|insert…> 或@Select 等由MappedStatement对象承载
  3. 缓存<cache…> 或@CacheNamespace 由Cache对象承载
  4. 结果集映射 由ResultMap 对象承载
    在这里插入图片描述

7.5 配置文件解析

配置文件解析需要我们分开讨论,首先来分析XML解析过程。xml配置解析其底层使用dom4j先解析成一棵节点树,然后根据不同的节点类型与去匹配不同的解析器。最终解析成特定组件。

解析器的基类是BaseBuilder 其内部包含全局的configuration 对象,这么做的用意是所有要解析的组件最后都要集中归属至configuration。接下来了解一下每个解析器的作用:

  • XMLConfigBuilder :解析config.xml文件,会直接创建一个configuration对象,用于解析全局配置 。
  • XMLMapperBuilder :解析Mapper.xml文件,内容包含 等
  • MapperBuilderAssistant:Mapper.xml解析辅助,在一个Mapper.xml中Cache是对Statement(sql声明)共享的,共享组件的分配即由该解析实现。
  • XMLStatementBuilder:SQL映射解析 即<select|update|insert|delete>元素解析成MapperStatement。
  • SqlSourceBuilder:Sql数据源解析,将声明的SQL解析可执行的SQL。
  • XMLScriptBuilder:解析动态SQL数据源当中所设置 SqlNode脚本集。
    在这里插入图片描述

7.6 XML文件解析流程

整体解析流程是从XmlConfigBuilder 开始,然后逐步向内解析,直到解析完所有节点。我们通过一个MappedStatement 解析过程即可了解到期整体解析流程。
在这里插入图片描述

流程说明:

  1. 【XmlConfigBuilder】 接收一个config.xml 输入流,然后创建一个空Configuration对象
  2. 【XmlConfigBuilder】解析全局配置
  3. 【XmlConfigBuilder】mapperElements解析,通过Resource或url 指定mapper.xml文件
    1. 【XmlMapperBuilder】解析缓存、结果集配置等公共配置
    2. 【XmlMapperBuilder】解析Sql映射<select|insert|upate|delete>
      1. 【XMLScriptBuilder】解析生成SQL数据源,包括动态脚本
    3. 【XmlMapperBuilder】构建Statement
      1. 【MapperBuilderAssistant】设置缓存并添加至Configuration

7.7 注解配置解析

注解解析底层实现是通过反射获取Mapper接口当中注解元素实现。有两种方式一种是直接指定接口名,一种是指定包名然后自动扫描包下所有的接口类。这些逻辑均由Mapper注册器(MapperRegistry)实现。其接收一个接口类参数,并基于该参数创建针对该接口的动态代理工厂,然后解析内部方法注解生成每个MapperStatement 最后添加至Configuration 完成解析。

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值