Mybatis 架构与原理 - litter-fish/ReadSource GitHub Wiki

MyBatis功能架构设计

把Mybatis的功能架构分为三层

  1. API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
  2. 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
  3. 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

框架架构

框架架构讲解:

MyBatis的初始化,会从mybatis-config.xml配置文件,解析构造成Configuration这个类,就是图中的红框

  1. 加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
  2. SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。
  3. SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
  4. 结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。

MyBatis成员层次&职责

说明

  1. SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
  2. Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
  3. StatementHandler 封装了JDBC Statement操作,负责对JDBCstatement的操作,如设置参数、将Statement结果集转换成List集合。
  4. ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数
  5. ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
  6. TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
  7. MappedStatement MappedStatement维护了一条<select|update|delete|insert>节点的封装
  8. SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
  9. BoundSql 表示动态生成的SQL语句以及相应的参数信息
  10. Configuration MyBatis所有的配置信息都维持在Configuration对象之中

一些重要对象

BaseBuilder

AdditionalParameter

TypeHandler

ObjectWrapperFactory:ObjectWrapperFactory是一个对象包装器工厂,用于对返回的结果对象进行二次处理,它主要在org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getRowValue方法中创建对象的MetaObject时作为参数设置进去,这样MetaObject中的objectWrapper属性就可以被设置为我们自定义的ObjectWrapper实现而不是mybatis内置实现

MetaObject:MetaObject是一个对象包装器,其性质上有点类似ASF提供的commons类库,其中包装了对象的元数据信息,对象本身,对象反射工厂,对象包装器工厂等。使得根据OGNL表达式设置或者获取对象的属性更为便利,也可以更加方便的判断对象中是否包含指定属性、指定属性是否具有getter、setter等。主要的功能是通过其ObjectWrapper类型的属性完成的,它包装了操作对象元数据以及对象本身的主要接口,操作标准对象的实现是BeanWrapper。BeanWrapper类型有个MetaClass类型的属性,MetaClass中有个Reflector属性,其中包含了可读、可写的属性、方法以及构造器信息

对象工厂ObjectFactory:MyBatis 每次创建结果对象的新实例时,都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂DefaultObjectFactory仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。如果想覆盖对象工厂的默认行为比如给某些属性设置默认值(有些时候直接修改对象不可行,或者由于不是自己拥有的代码或者改动太大),则可以通过创建自己的对象工厂来实现

MappedStatement:mapper文件或者mapper接口中每个映射语句都对应一个MappedStatement实例,它包含了所有运行时需要的信息比如结果映射、参数映射、是否需要刷新缓存等。

ParameterMapping:每个参数映射<>标签都被创建为一个ParameterMapping实例,其中包含和结果映射类似的信息

KeyGenerator

public interface KeyGenerator {
  // before key generator 主要用于oracle等使用序列机制的ID生成方式
  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
  // after key generator 主要用于mysql等使用自增机制的ID生成方式
  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}

Registry:mybatis将类型处理器,类型别名,mapper定义,语言驱动器等各种信息包装在Registry中维护

public class Configuration {
  ...
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  ...
}

ResultMap:ResultMap类维护了每个标签中的详细信息,比如id映射、构造器映射、属性映射以及完整的映射列表、是否有嵌套的resultMap、是否有鉴别器、是否有嵌套查询

ResultMapping

Discriminator

public class Discriminator {

  // 所属的属性节点<result>
  private ResultMapping resultMapping;
  // 内部的if then映射
  private Map<String, String> discriminatorMap;
}

Configuration:Configuration是mybatis所有配置以及mapper文件的元数据容器。无论是解析mapper文件还是运行时执行SQL语句,都需要依赖与mybatis的环境和配置信息,比如databaseId、类型别名等。mybatis实现将所有这些信息封装到Configuration中并提供了一系列便利的接口方便各主要的调用方使用,这样就避免了各种配置和元数据信息到处散落的凌乱。

public class Configuration {

  protected Environment environment;

  // 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。???????
  protected boolean safeRowBoundsEnabled;

  // 允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为 false ??????
  protected boolean safeResultHandlerEnabled = true;

  // 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。
  protected boolean mapUnderscoreToCamelCase;

  // 当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载
  protected boolean aggressiveLazyLoading;

  // 是否允许单一语句返回多结果集(需要驱动支持)
  protected boolean multipleResultSetsEnabled = true;

  // 允许 JDBC 支持自动生成主键,需要驱动支持。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能支持但仍可正常工作
  protected boolean useGeneratedKeys;

  // 使用列标签代替列名
  protected boolean useColumnLabel = true;

  // 全局地开启或关闭缓存
  protected boolean cacheEnabled = true;

  // 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值初始化的时候比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。
  protected boolean callSettersOnNulls;

  // 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项
  protected boolean useActualParamName = true;

  // 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集 (如集合或关联)。
  protected boolean returnInstanceForEmptyRow;

  // 指定 MyBatis 增加到日志名称的前缀
  protected String logPrefix;

  // 指定 MyBatis 所用日志的具体实现,未指定时将自动查找
  // SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
  protected Class <? extends Log> logImpl;

  // 指定VFS的实现, VFS是mybatis提供的用于访问AS内资源的一个简便接口
  protected Class <? extends VFS> vfsImpl;

  // MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。
  // 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。
  // 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;

  // 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;

  // 指定哪个对象的方法触发一次延迟加载。 用逗号分隔的方法列表。
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));

  // 设置超时时间,它决定驱动等待数据库响应的秒数
  protected Integer defaultStatementTimeout;

  // 为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。
  protected Integer defaultFetchSize;

  // 配置默认的执行器。
  // SIMPLE 就是普通的执行器;
  // REUSE 执行器会重用预处理语句(prepared statements);
  // BATCH 执行器将重用语句并执行批量更新。
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

  // 指定 MyBatis 应如何自动映射列到字段或属性。
  // NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;

  // 指定发现自动映射目标未知列(或者未知属性类型)的行为。
  // NONE: 不做任何反应
  // WARNING: 输出提醒日志 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN)
  // FAILING: 映射失败 (抛出 SqlSessionException)
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

  // settings下的properties属性
  protected Properties variables = new Properties();

  // 默认的反射器工厂,用于操作属性、构造器方便
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();

  // 对象工厂, 所有的类resultMap类都需要依赖于对象工厂来实例化
  protected ObjectFactory objectFactory = new DefaultObjectFactory();

  // 对象包装器工厂,主要用来在创建非原生对象,比如增加了某些监控或者特殊属性的代理类
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  // 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
  protected boolean lazyLoadingEnabled = false;

  // 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。  CGLIB | JAVASSIST
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  // MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
  protected String databaseId;
  /**
   * Configuration factory class.
   * Used to create Configuration for loading deserialized unread properties.
   *
   * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
   */
  // 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。
  // 这个类必须包含一个签名为static Configuration getConfiguration() 的方法
  protected Class<?> configurationFactory;

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

  // mybatis插件列表
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  .......
}

ErrorContext:ErrorContext定义了一个mybatis内部统一的日志规范,记录了错误信息、发生错误涉及的资源文件、对象、逻辑过程、SQL语句以及出错原因 通过单例模式创建实例

public class ErrorContext {

  private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");
  private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
    
  private ErrorContext() {
  }

  public static ErrorContext instance() {
    ErrorContext context = LOCAL.get();
    if (context == null) {
      context = new ErrorContext();
      LOCAL.set(context);
    }
    return context;
  }
}

BoundSql

/*
 * SqlSource中包含的SQL处理动态内容之后的实际SQL语句,SQL中会包含?占位符,也就是最终给JDBC的SQL语句,以及他们的参数信息
 */
public class BoundSql {

  // sql文本
  private final String sql;
  // 静态参数说明
  private final List<ParameterMapping> parameterMappings;
  // 运行时参数对象
  private final Object parameterObject;
  // 额外参数,也就是for loops、bind生成的
  private final Map<String, Object> additionalParameters;
  // 额外参数的facade模式包装
  private final MetaObject metaParameters;
}

重要类的解释

  1. CachingExecutor:二级缓存执行类
  2. SimpleExecutor:包括ReuseExecutor,BatchExecutor这三种,这个是可以通过配置选择不同的执行器的。默认的也就是常用的就是SimpleExecutor它们继承至BaseExecutor
  3. PreparedStatementHandler:设置参数,并且获取数据库返回的数据,并且将参数传给DefaultResultSetHandler,将数据库返回的数据封装成我们设置的数据。

四大核心接口对象

执行器Executor,执行器负责整个SQL执行过程的总体控制。

ExecutorType.SIMPLE:这个执行器类型不做特殊的事情。它为每个语句的每次执行创建一个新的预处理语句。 ExecutorType.REUSE:这个执行器类型会复用预处理语句。 ExecutorType.BATCH:这个执行器会批量执行所有更新语句,也就是jdbc addBatch API的facade模式。

参数处理器ParameterHandler,参数处理器负责PreparedStatement入参的具体设置。

语句处理器StatementHandler,语句处理器负责和JDBC层具体交互,包括prepare语句,执行语句,以及调用ParameterHandler.parameterize()设置参数。

结果集处理器ResultSetHandler,结果处理器负责将JDBC查询结果映射到java对象

⚠️ **GitHub.com Fallback** ⚠️