Mybatis技术内幕笔记(二)

Scroll Down

基础支持层

基础支持层位于Mybatis整体架构的最底层,支撑着Mybatis的核心处理层,是整个框架的基石。基础支持层中封装了多个较为通用的、独立的模块,不仅仅为Mybatis提供基础支撑,也可以在合适的场景中直接复用。

解析器模块

在Mybatis中涉及多个XML配置文件,因此我们首先介绍XML解析的相关内容。XML解析常见的方式有三种,分别是:DOM(Document Object Model)解析方式和SAX(Simple API for XML)解析方式,以及从JDK6.0版本开始,JDK开始支持StAX (Streaming API for XML)解析方式。

  • DOM
    DOM是基于树形结构的XML解析方式,它会将整个XML文档读入内存并构建一个DOM树,基于这棵树形结构对各个节点(Node)进行操作。XML文件中的每个成分都是一个节点:整个文档是一个文档节点,每个XML标签对应一个元素节点,包含在XML标签中的文本是文本节点,每一个XML属性是一个属性节点,注释属于注释节点。
    DOM解析方式最主要的好处是易于编程,可以根据需求在树形结构的各节点之间导航。
  • SAX
    SAX是基于事件模型的XML解析方式,它并不需要将整个XML文档加载到内存中,而只需将XML文件的一部分加载到内存中,即可开始解析,在处理过程中并不会在内存中记录XML中的数据,所以占用资源比较小。当程序处理过程中满足条件时,也可以立即停止解析过程,这样就不必解析剩余的XML内容。
    当SAX解析器解析到某类型节点时,会触发注册在该类型节点上的回调函数,开发人员可以根据自己感兴趣的事件注册相应的回调函数。一般情况下,开发人员只需继承SAX提供的DefaultHandler基类,重写相应事件的处理方法并进行注册即可。事件是由解析器产生并通过回调函数发送给应用程序的,这种模式我们也称为推模式。

XPath简介

Mybatis在初始化过程中处理mybatis-config.xml配置文件以及映射文件时,使用的是DOM解析方式,并结合使用XPath解析XML配置文件。DOM会将整个XML文档加载到内存中并形成树状数据结构,而XPath是一种查询XML文档而设计的语言,它可以与DOM解析方式配合使用,实现对XML文档的解析。
XPath使用路径表达式来选取XML文档中指定的节点或者节点集合。

表达式含义
nodename选取指定节点的所有子节点
/从根节点选取指定节点
//根据指定的表达式,在整个文档中选取匹配的节点,这里并不会考虑匹配节点在文档中的位置
.选取当前节点
..选取当前节点的父节点
@选取属性
*匹配任何元素节点
@*匹配任何属性节点
node()匹配任何类型的节点
text()匹配文本节点
1选取若干路径
[]指定某个条件,用于查找某个特定节点或包含某个指定值的节点

在JDK5.0版本中推出了javax.xml.xpath包,它是一个引擎和对象模型独立的XPath库。Java中使用XPath变成的代码模式比较固定。

XPathParser

Mybatis提供的XPathParser类封装了前面涉及的XPath、Document和EntityResolver。
XPathParser中各个字段的含义和功能。

private final Document document; // Document对象
private boolean validation; // 是否开启验证
private EntityResolver entityResolver; // 用于加载本地DTD文件
private Properties variables; // mybatis-config.xml中<properties>标签定义的键值对集合
private XPath xpath; // XPath对象

默认情况下,对DML文档进行验证时,会根据XML文档开始位置指定的网址加载对应的DTD文件或者XSD文件。如果解析mybatis-config.xml配置文件,默认联网加载http://mybatis.org/dtd/mybatis-3-config.dtd这个DTD文档,当网络比较慢时会导致验证过程缓慢。在实践中往往会提前设置EntityResolver接口对象加载本地的DTD文件,从而避免联网加载DTD文件。XMLMapperEntityResolver是Mybatis提供的EntityResolver接口的实现类。
实现如下:

// 指定mybatis-config.xml文件和映射文件对应的DTD的SystemId
private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";

// 指定mybatis-config.xml文件和映射文件对应的DTD文件的具体位置
private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";

// resolveEntity()方法是EntityResolver接口中定义的方法,具体实现如下:
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
    try {
      if (systemId != null) {
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
	// 查找systemId指定的DTD文档,并调用getInputSource()方法读取DTD文档
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
        } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
          return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
        }
      }
      return null;
    } catch (Exception e) {
      throw new SAXException(e.toString());
    }
}

XPathParser。createDocument()方法中封装了创建Document对象过程中并触发了加载XML文档的过程,具体实现如下:

public XPathParser(String xml) {
    commonConstructor(false, null, null);
    this.document = createDocument(new InputSource(new StringReader(xml)));
}

// 调用createDocument()方法之前一定要先调用commonConstructor()方法完成初始化
private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      // 创建DocumentBuilderFactory对象
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);
      // 创建DocumentBuilder对象并进行配置
      DocumentBuilder builder = factory.newDocumentBuilder();
      // 设置EntityResolver对象并进行配置
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
        }
      });
      // 加载XML文件
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
}

XPathParser中提供了一系列的eval*()方法用于解析boolean、short、long、int、String、Node等类型的信息,它通过前面介绍的XPath。evaluate()方法查找指定路径的节点或属性,并进行相应的类型转换。
GenericTokenParser是一个通用的字占位符解析器,其字段的含义如下:

private final String openToken; // 占位符的开始标记
private final String closeToken; // 占位符的结束标记

private final TokenHandler handler; // TokenHandler接口的实现会按照一定的逻辑解析占位符

GenericTokenParser.parse()方法的逻辑,它会顺序查找openToken和closeToken,解析得到的占位符的字面值,并将其交给TokenHandler处理,然后将解析结果重新拼装成字符串并返回。

public String parse(String text) {
    if (text == null || text.isEmpty()) {
      return "";
    }
    // search open token 查找开始标记
    int start = text.indexOf(openToken, 0);
    // 检测start是否为-1
    if (start == -1) {
      return text;
    }
    char[] src = text.toCharArray();
    int offset = 0;
    // 用于记录解析后的字符串
    final StringBuilder builder = new StringBuilder();
    // 用于记录一个占位符的字面值
    StringBuilder expression = null;
    while (start > -1) {
      if (start > 0 && src[start - 1] == '\\') {
        // this open token is escaped. remove the backslash and continue.
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
        // found open token. let's search close token.
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        builder.append(src, offset, start - offset);
        offset = start + openToken.length();
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {
          if (end > offset && src[end - 1] == '\\') {
            // 遇到转义的开始标记,则直接将前面的字符串以及开始标记追加到builder中
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {
            // 查找到开始标记,且未转义
            expression.append(src, offset, end - offset);
            offset = end + closeToken.length();
            break;
          }
        }
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          // 将占位符的字面值交给TokenHandler处理,并将处理结果追加到builder中保存。
          // 最终拼凑出解析后的完整内容。
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      // 移动start
      start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
}

XPathParser.evalNode()方法返回值类型是XNode,它是对org.w3c.dom.Node对象做了封装和解析,其各个字段的含义如下:

private final Node node; // org.w3c.dom.Node对象
private final String name; // Node节点名称
private final String body; // 节点内容
private final Properties attributes; // 节点属性集合
private final Properties variables; // mybatis-config.xml配置文件中<properties>节点下定义的键值对
private final XPathParser xpathParser;

反射工具箱

Reflector&ReflectorFactory

Reflector是Mybatis中反射模块的基础,每个Reflector对象都对应一个类,在Reflector中缓存了反射操作都需要使用的类的元信息。Reflector中各个字段的含义如下:

private final Class<?> type; // 对应的class类型
private final String[] readablePropertyNames; // 可读属性的名称集合,可读属性就是存在相应的getter方法的属性,初始值为空数组
private final String[] writeablePropertyNames; // 可写属性的名称集合,可写属性就是存在相应的setter方法的属性,初始值为空数组
// 记录了属性相应的setter方法,key是属性名称,value是Invoker对象,它是对setter方法对应的Method对象的封装
private final Map<String, Invoker> setMethods = new HashMap<String, Invoker>(); 
// 记录了属性相应的getter方法,key是属性名称,value是Invoker对象,它是对getter方法对应的Method对象的封装
private final Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
// 记录了属性相应的setter方法的参数值类型,key是属性名称,value是setter方法的参数类型
private final Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
// 记录了属性相应的getter方法的参数值类型,key是属性名称,value是getter方法的参数类型
private final Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
// 记录了默认构造方法
private Constructor<?> defaultConstructor;
// 记录了所有属性名称的集合
private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();

在Reflector的构造方法中会解析指定的Class对象,并填充上述集合,具体代码如下:

public Reflector(Class<?> clazz) {
    type = clazz; // 初始化type字段
    // 查找clazz的默认构造方法
    addDefaultConstructor(clazz);
    // 处理clazz中的getter方法,填充getMethods集合和getTypes集合
    addGetMethods(clazz);
    // 处理clazz中的setter方法,填充setMethods集合和setTypes集合
    addSetMethods(clazz);
    // 处理没有setter/getter方法的字段
    addFields(clazz);
    // 根据getMethods/setMethods集合,初始化可读/可写属性的名称集合
    readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
    writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
    // 初始化caseInsensitivePropertyMap集合,其中记录了所有大写格式的属性名称
    for (String propName : readablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writeablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
}

Reflector.addGetMethods()方法主要负责解析类中定义的getter方法,Referlector.addSetMethods()方法负责解析类中定义的setter方法,两者的逻辑类似。
Reflector.addGetMethods()方法有三个核心步骤:

  1. 首先,调用Reflector.getClassMethods()方法获取当前类以及其父类中定义的所有方法的唯一签名以及相应的Method对象。
private Method[] getClassMethods(Class<?> cls) {
    // 用于记录指定类中定义的全部方法的唯一签名以及对应的Method对象
    Map<String, Method> uniqueMethods = new HashMap<String, Method>();
    Class<?> currentClass = cls;
    while (currentClass != null && currentClass != Object.class) {
      // 记录currentClass这个类中定义的全部方法
      addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
      // 记录接口中定义的方法
      // we also need to look for interface methods -
      // because the class may be abstract
      Class<?>[] interfaces = currentClass.getInterfaces();
      for (Class<?> anInterface : interfaces) {
        addUniqueMethods(uniqueMethods, anInterface.getMethods());
      }
      // 获取父类,继续while循环
      currentClass = currentClass.getSuperclass();
    }

    Collection<Method> methods = uniqueMethods.values();
    // 转换成Methods数组返回
    return methods.toArray(new Method[methods.size()]);
}

在Reflector.addUniqueMethods()方法中会为每个方法生成唯一签名,并记录到uniqueMethods集合中,具体实现如下:

private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
    for (Method currentMethod : methods) {
      if (!currentMethod.isBridge()) {
        // 通过Reflector.getSignature()方法得到的方法签名是,返回值类型#方法名称:参数类型列表
        // 通过Reflector.getSignature()方法得到的方法签名是全局唯一的,可以作为该方法的唯一标识
        String signature = getSignature(currentMethod);
        // check to see if the method is already known
        // if it is known, then an extended class must have
        // overridden a method
        // 检测是否在子类中已经添加过该方法,如果在子类中已经添加过,则表示子类覆盖了该方法,
        // 无须再向uniqueMethods集合中添加该方法了
        if (!uniqueMethods.containsKey(signature)) {
          if (canAccessPrivateMethods()) {
            try {
              currentMethod.setAccessible(true);
            } catch (Exception e) {
              // Ignored. This is only a final precaution, nothing we can do.
            }
          }
          // 记录该签名和方法的对应关系
          uniqueMethods.put(signature, currentMethod);
        }
      }
    }
}
  1. 然后,按照JavaBean的规范,从Reflector.getClassMethods()方法返回的Method数组中查找该类中定义的getter方法,将其记录到key为属性名称,value是该属性对应的getter方法集合。
  2. 当子类覆盖了父类的getter方法且返回值发生变化时,在步骤一中就会产生两个签名不同的方法。
    有一点需要注意的是,addMethod()方法和addField()方法在向上述集合添加元素时,会将getter/setter方法对应的Method对象以及字段对应的Field对象统一封装成Invoker对象。Invoker接口的定义如下所示:
public interface Invoker {
  // 调用获取指定字段的值或执行指定的方法,通过下文对Invoker接口实现的介绍,可以更好地理解该方法
  Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;
  // 返回属性相应的类型
  Class<?> getType();
}

Invoker接口的实现如图所示:
image.png
GetFieldInvoker/SetFieldInvoker通过field字段封装了对应的Field对象,两者的invoke()方法通过调用Field.get()/set()方法实现的。MethodInvoker通过method字段封装了对应方法的Method对象,其invoke()方法是通过调用Method.invoke方法实现的。
ReflectorFactory接口主要实现了对Reflector对象的创建和缓存,该接口定义如下:

public interface ReflectorFactory {

  boolean isClassCacheEnabled(); // 检测该ReflectorFactory对象是否缓存Reflector对象

  void setClassCacheEnabled(boolean classCacheEnabled); // 设置是否缓存Reflector对象

  Reflector findForClass(Class<?> type); // 创建指定Class对应的Reflector对象
}

Mybatis只为该接口提供了DefaultReflectorFactory这一实现类。主要字段如下:

// 该字段决定是否开启对Reflector对象的缓存
private boolean classCacheEnabled = true;
// 使用ConcurrentMap集合实现对Reflector对象的缓存
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<Class<?>, Reflector>();

DefaultReflectorFactory提供的findForClass()方法实现为指定的Class创建Reflector对象,并将Reflector对象缓存到reflectorMap中,具体代码如下:

@Override
public Reflector findForClass(Class<?> type) {
    if (classCacheEnabled) { // 检测是否开启缓存
      // synchronized (type) removed see issue #461
      Reflector cached = reflectorMap.get(type);
      if (cached == null) {
        cached = new Reflector(type); // 创建Reflector对象
        reflectorMap.put(type, cached); // 放入ConcurrentMap缓存
      }
      return cached;
    } else {
      return new Reflector(type); // 未开启缓存,则直接创建并返回Reflector对象
    }
}

除了使用Mybatis提供的DefaultReflectorFactory实现,我们还可以在mybatis-config.xml中配置自定义的ReflectorFactory实现类,从而实现功能上的扩展。

TypeParameterResolver

image.png

  • Class比较常见,它表示的是原始类型。Class类的对象表示JVM中的一个类或接口,每个Java类在JVM里面都表现为一个Class对象。在程序中可以通过“类名.class”、“对象.getClass()”或是“Class.forName("类名")”等方式获取Class对象。数组也被映射为Class对象,所有元素类型相同且维数相同的数组都共享同一个Class对象。
  • ParameterizedType表示的是参数化类型,例如List、Map<Integer,String>这种带有泛型的类型。
    ParameterizedType接口中常用的方法有三个,分别是:
  1. Type.getRawType()-返回参数化类型中的原始类型,例如List的原始类型为List。
  2. Type[] getActualTypeArguments()-获取参数化类型的类型变量或是实际类型列表。例如Map<Integer,String>的实际泛型列表Integer和String。需要注意的是,该列表的元素类型都是Type,也就是说,可能存在多层嵌套的情况。
  3. Type getOwnerType()-返回是类型所属的类型,例如存在A类,其中定义了内部类InnerA,则InnerA所属的类型为A,如果是顶层类型则返回null。这种关系示例是,Map<K,V>接口与Map.Entry<K,V>接口,Map<K,V>接口是Map.Entry<K,V>接口的所有者。
  • TypeVariable表示的是类型变量,它用来反映在JVM编译该泛型前的信息。例如List中的T就是类型变量,它在编译时需被转换为一个具体的类型后才能正常使用。
    该接口中常用的方法有三个,分别是:
  1. Type[] getBounds()-获取类型变量的上边界,如果未明确声明上边界则默认为Object。例如class Test中K的上边界就是Person。
  2. D getGenericDeclaration()-获取声明该类型变量的原始类型,例如class Test中的原始类型就是Test.
  3. String getName()-获取源码中定义是的名字,上例中为K。
  • GenericArrayType表示的是数组类型且组成元素是ParameterizedType或TypeVariable。例如List[]或T[],该接口只有Type getGenericComponentType()一个方法。它返回数组的组成元素。
  • WildcardType表示的是通配符泛型,例如? extends Number和? super Integer。
    WildcardType接口有两个方法,分别是:
  1. Type[] getUpperBounds()-返回泛型变量的上界
  2. Type[] getLowerBounds()-返回反向变量的下界。
    TypeParameterResolver中通过resolveFieldType()方法、resolveReturnType()方法、resolveParamTypes()方法分别解析字段类型、方法返回值类型和方法参数列表中各个参数的类型。这三个方法的逻辑基本类似。
    resolveFieldType()方法具体实现如下:
public static Type resolveFieldType(Field field, Type srcType) {
    Type fieldType = field.getGenericType(); // 获取字段的声明类型
    // 获取字段定义所在的类的Class对象
    Class<?> declaringClass = field.getDeclaringClass();
    // 调用resolveType()方法进行后续处理
    return resolveType(fieldType, srcType, declaringClass);
}

上述三个方法都会调用resolveType()方法,该方法会根据第一个参数的类型,即字段、方法返回值或方法参数的类型,选择合适的方法进行解析、resolveType()方法的第二个参数表示查找该字段、返回值或方法参数的起始位置。第三个参数则表示该字段、方法所在的类。TypeParameterResolver.resolveType()方法的代码如下:

private static Type resolveType(Type type, Type srcType, Class<?> declaringClass) {
    if (type instanceof TypeVariable) { // 解析TypeVariable类型
      return resolveTypeVar((TypeVariable<?>) type, srcType, declaringClass);
    } else if (type instanceof ParameterizedType) { // 解析ParameterizedType类型
      return resolveParameterizedType((ParameterizedType) type, srcType, declaringClass);
    } else if (type instanceof GenericArrayType) { // 解析GenericArrayType类型
      return resolveGenericArrayType((GenericArrayType) type, srcType, declaringClass);
    } else {
      return type;
    }
}
ObjectFactory

Mybatis中有很多模块会使用到ObjectFactory接口,该接口提供了多个create()方法的重载,通过这些create()方法可以创建指定类型的对象。ObjectFactory接口的定义如下:

public interface ObjectFactory {

  /**
   * Sets configuration properties.
   * @param properties configuration properties
   */
  void setProperties(Properties properties); // 设置配置信息

  /**
   * Creates a new object with default constructor. 
   * @param type Object type
   * @return
   */
  <T> T create(Class<T> type); // 通过无参构造函数创建指定类的对象

  /**
   * Creates a new object with the specified constructor and params.
   * @param type Object type
   * @param constructorArgTypes Constructor argument types
   * @param constructorArgs Constructor argument values
   * @return
   */
  <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs); // 根据参数列表,从指定类型中选择合适的构造器对象
  
  /**
   * Returns true if this object can have a set of other objects.
   * It's main purpose is to support non-java.util.Collection objects like Scala collections.
   * 
   * @param type Object type
   * @return whether it is a collection or not
   * @since 3.1.0
   */
  <T> boolean isCollection(Class<T> type); // 检测指定类型是否为集合类型,主要处理java.util.Collection及其子类

}

DefaultObjectFactory是Mybatis提供的ObjectFactory接口的唯一实现,它是一个反射工厂,其create()方法通过调用instantiateClass()方法实现。DefaultObjectFactory.instantiateClass()方法会根据传入的参数列表选择合适的构造函数实例化对象,具体实现如下:

private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    try {
      Constructor<T> constructor;
      // 根据无参构造函数创建对象
      if (constructorArgTypes == null || constructorArgs == null) {
        constructor = type.getDeclaredConstructor();
        if (!constructor.isAccessible()) {
          constructor.setAccessible(true);
        }
        return constructor.newInstance();
      }
      // 根据指定的参数列表查找构造函数,并实例化对象
      constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
      if (!constructor.isAccessible()) {
        constructor.setAccessible(true);
      }
      return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
    } catch (Exception e) {
      StringBuilder argTypes = new StringBuilder();
      if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
        for (Class<?> argType : constructorArgTypes) {
          argTypes.append(argType.getSimpleName());
          argTypes.append(",");
        }
        argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
      }
      StringBuilder argValues = new StringBuilder();
      if (constructorArgs != null && !constructorArgs.isEmpty()) {
        for (Object argValue : constructorArgs) {
          argValues.append(String.valueOf(argValue));
          argValues.append(",");
        }
        argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
      }
      throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
    }
}

除了使用Mybatis提供的DefaultObjectFactory实现,我们还可以在mybatis-config.xml配置文件中指定了自定义的ObjectFactory接口实现类,从而实现功能上的扩展。

Property工具集

主要介绍反射模块中使用到的三个属性工具类,分别是PropertyTokenizer、PropertyNamer和PropertyCopier。
例如orders[0].items[0].name这种由“.”和“[]”组成的表达式是由PropertyTokenizer进行解析的,PropertyTokenizer中各字段的含义如下:

// 当前表达式的名称
private String name;
// 当前表达式的索引名
private final String indexedName;
// 索引下标
private String index;
// 子表达式
private final String children;

在PropertyTokenizer的构造方法中会对传入的表达式进行分析,并初始化上述字段,具体实现如下:

public PropertyTokenizer(String fullname) {
    // 查找“.”的位置
    int delim = fullname.indexOf('.');
    if (delim > -1) {
      name = fullname.substring(0, delim); // 初始化name
      children = fullname.substring(delim + 1); // 初始化children
    } else {
      name = fullname;
      children = null;
    }
    indexedName = name; // 初始化indexName
    delim = name.indexOf('[');
    if (delim > -1) {
      index = name.substring(delim + 1, name.length() - 1); // 初始化index
      name = name.substring(0, delim);
    }
}

PropertyTokenizer继承了Iterator接口,它可以迭代处理嵌套多层表达式。PropertyTokenizer.next()方法中会创建新的PropertyTokenizer对象并解析children字段记录的子表达式。
PropertyNamer是另一个工具类,提供了下列静态方法帮助完成方法名到属性名的转换,以及多种检测操作。

public final class PropertyNamer {

  private PropertyNamer() {
    // Prevent Instantiation of Static Class
  }

  // 将方法名称转换成属性名
  public static String methodToProperty(String name) {
    if (name.startsWith("is")) {
      name = name.substring(2);
    } else if (name.startsWith("get") || name.startsWith("set")) {
      name = name.substring(3);
    } else {
      throw new ReflectionException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
    }

    if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
      name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
    }

    return name;
  }
  // 负责检测方法名是否对应属性名
  public static boolean isProperty(String name) {
    return name.startsWith("get") || name.startsWith("set") || name.startsWith("is");
  }
  // 检测方法是否为getter方法
  public static boolean isGetter(String name) {
    return name.startsWith("get") || name.startsWith("is");
  }
  // 检测方法是否为setter方法
  public static boolean isSetter(String name) {
    return name.startsWith("set");
  }

}

PropertyCopier是一个属性拷贝的工具类,其核心方法是copyBeanPropertys()方法,主要实现相同类型的两个对象之间的属性拷贝。

MetaClass

MetaClass通过Reflector和PropertyTokenizer组合使用,实现了对复杂属性表达式的解析,并实现了获取指定属性描述信息的功能。MetaClass中各个字段的含义如下:

// ReflectorFactory对象,用于缓存Reflector对象
private final ReflectorFactory reflectorFactory;
// 在创建MetaClass时会指定一个类,该Reflector对象会用于记录该类相关的元信息
private final Reflector reflector;

MetaClass在构造函数中会为指定的Class创建相应的Reflector对象,并用其初始化MetaClass.reflector字段,具体代码如下:

// MetaClass的构造方法是使用private修饰的
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
    this.reflectorFactory = reflectorFactory;
    // 创建Reflector对象,DefaultReflectorFactory.findForClass()
    this.reflector = reflectorFactory.findForClass(type);
}
ObjectWrapper

MetaClass是Mybatis对类级别的元信息的封装和处理。ObjectWrapper接口是对对象的包装,抽象了对象的属性信息,它定义了一系列的查询对象属性信息的方法,以及更新属性的方法,

public interface ObjectWrapper {

  // 如果ObjectWrapper中封装的是普通的Bean对象,则调用相应属性的相应getter方法,
  // 如果封装的是集合类,则获取指定key或下标对应的value值
  Object get(PropertyTokenizer prop);

  // 如果ObjectWrapper中封装的是普通的Bean对象,则调用相应属性的相应setter方法,
  // 如果封装的是集合类,则设置指定key或下标对应的value值
  void set(PropertyTokenizer prop, Object value);

  // 查找属性表达式指定的属性,第二个参数表示是否忽略属性表达式中的下划线
  String findProperty(String name, boolean useCamelCaseMapping);

  // 查找可写属性的名称集合
  String[] getGetterNames();
  
  // 查找可读属性的名称集合
  String[] getSetterNames();

  // 解析属性表达式指定属性的setter方法的参数类型
  Class<?> getSetterType(String name);

  // 解析属性表达式指定属性的getter方法的参数类型
  Class<?> getGetterType(String name);

  // 判断属性表达式指定属性是否有getter/setter方法
  boolean hasSetter(String name);
  boolean hasGetter(String name);

  // 为属性表达式指定的属性创建相应的MetaObject对象
  MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);
  
  // 封装对象是否为Collection类型
  boolean isCollection();
  
  // 调用Collection对象的add()方法
  void add(Object element);
  
  // 调用Collection对象的addAll()方法
  <E> void addAll(List<E> element);

}

ObjectWrapperFactory负责创建ObjectWrapper对象
image.png
DefaultObjectWrapperFactory实现了ObjectWrapperFactory接口,但它实现的getWrapperFor()方法始终抛出异常,hasWrapperFor()方法始终返回false,所以该实现实际上是不可用的。但是与ObjectFactory类似,我们可以在mybatis-config.xml中配置自定义的ObjectWrapperFactory实现类进行扩展。
BaseWrapper是一个实现了ObjectWrapper接口的抽象类,其中封装了MetaObject对象,并提供了三个常用的方法供其子类使用。
BaseWrapper.resolveCollection()方法会调用MetaObject.getValue()方法,它会解析属性表达式获取指定的属性。
BaseWrapper.getCollectionValue()方法和setCollectionValue()方法会解析属性表达式的索引信息,然后获取/设置对应项。

类型转换

JDBC数据类型与Java语言中的数据类型并不是完全对应的,所以在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换为JDBC类型,而从结果集中获取数据时,则需要从JDBC类型转换成Java类型。Mybatis使用类型处理器完成上述两种转换。
首先需要了解的是,在Mybatis中使用JdbcType这个枚举类型代表JDBC中的数据类型,该枚举类型中定义了TYPE_CODE字段,记录了JDBC类型在java.sql.Types中相应的常量编码,并通过一个静态集合codeLookup维护了常量编码与jdbcType之间的对应关系。

  1. TypeHandler
    Mybatis中所有的类型转换器都继承了TypeHandler接口,在TypeHandler接口中定义了如下四个方法,这四个方法分为两类,setParameter()方法负责将数据由JdbcType类型转换成java类型;getResult()方法负责将数据由Java类型转换成JdbcType类型。
public interface TypeHandler<T> {
  // 在通过PreparedStatement为SQL语句绑定参数时,会将数据由JdbcType类型转换为Java类型
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  // 从ResultSet中获取数据时会调用此方法,会将数据由Java类型转换成JdbcType类型
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

为了方便用户自定义TypeHandler实现,Mybatis提供了BaseTypeHandler这个抽象类,它实现了TypeHandler接口,并继承了TypeReference抽象类。
在BaseTypeHandler中实现了TypeHandler.setParameter()方法和TypeHandler.getResult()方法。
一般情况下,TypeHandler用于完成单个参数以及单个列值的类型转换,如果存在多列值转换成一个Java对象的需求,应该优先考虑使用在映射文件中定义合适的映射规则resultMap完成映射。

TypeAliasRegistry

在编写SQL语句时,使用别名可以方便理解以及维护,例如表名或列名很长时,我们一般会为其设计易懂易维护的别名。Mybatis将SQL语句中别名的概念进行了延伸和扩展,Mybatis可以为一个类添加一个别名,之后就可以通过别名引用该类。
Mybatis通过TypeAliasRegistry类完成别名注册和管理功能,TypeAliasRegistry的结构比较简单,它通过TYPE_ALIASES字段管理别名与Java类型之间的对应关系,通过TypeAliasRegistry.registerAlias()方法完成注册别名,该方法的具体实现如下:

public void registerAlias(String alias, Class<?> value) {
    // 检测alias为null,则直接抛出异常
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    String key = alias.toLowerCase(Locale.ENGLISH); // 将别名转换为小写
    // 检测别名是否已经存在
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    TYPE_ALIASES.put(key, value); // 注册别名
}

日志模块

在Java开发中常用的日志框架有Log4j、Log4j2、Apache Commons Log、java.util.logging、slf4j等,这些工具对外的接口不尽相同。为了统一这些工具的接口,Mybatis定义了一套统一的日志接口供上层使用,并为了上述常用的日志框架提供了相应的适配器。

日志适配器

Mybatis统一提供了trace、debug、warn、error四个级别。
Mybatis的日志模块位于org.apache.ibatis.logging包中,该模块中通过Log接口定义了日志模块的功能,当然日志适配器也会实现此接口。LogFactory工厂类负责创建对应的日志组件适配器。

DataSource

在数据持久层中,数据源是一个非常重要的组件,其性能直接关系到整个数据持久层的性能。在实践中比较常见的第三方数据源组件有Apache Common DBCP、C3P0、Protocol等、Mybatis不仅可以集成第三方数据源组件,还通过了自己的数据源实现。
常见的数据源组件都实现了javax.sql.DataSource接口,Mybatis自身实现的数据源实现也不例外。Mybatis提供了两个javax.sql.DataSource接口实现。分别是PooledDataSource和UnpooledDataSource。Mybatis使用不同的DataSourceFactory接口实现创建不同类型的DataSource。

DataSourceFactory

在数据源模块中,DataSourceFactory接口扮演工厂的角色。UnpooledDataSourceFactory和PooledDataSourceFactory则扮演着具体工厂类的角色。

public interface DataSourceFactory {

  // 设置DataSource的相关属性,一般紧跟在初始化完成之后
  void setProperties(Properties props);
  // 获取DataSource对象
  DataSource getDataSource();

}

在UnpolledDataSourceFactory的构造函数中会直接创建UnpooledDataSource对象,并初始化UnpooledDataSourceFactory.dataSource字段。UnpooledDataSourceFactory.setProperties()方法会完成对UnpooledDataSource对象的配置,代码如下:

@Override
public void setProperties(Properties properties) {
    Properties driverProperties = new Properties();
    // 创建DataSource相应的MetaObject
    MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
    // 遍历properties集合,该集合中配置了数据源需要的信息
    for (Object key : properties.keySet()) {
      String propertyName = (String) key;
      if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
        String value = properties.getProperty(propertyName);
        // 以“driver.”开头的配置项是对DataSource的配置,记录到driverProperties中保存
        driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
      } else if (metaDataSource.hasSetter(propertyName)) { // 是否有该属性的setter方法
        String value = (String) properties.get(propertyName);
        // 根据属性类型进行类型转换,主要是Integer、Long、Boolean三种类型的转换
        Object convertedValue = convertValue(metaDataSource, propertyName, value);
        // 设置DataSource的相关属性值
        metaDataSource.setValue(propertyName, convertedValue);
      } else {
        throw new DataSourceException("Unknown DataSource property: " + propertyName);
      }
    }
    if (driverProperties.size() > 0) { // 设置DataSource.driverProperties属性值
      metaDataSource.setValue("driverProperties", driverProperties);
    }
}

PooledDataSourceFactory继承了UnpooledDataSourceFactory,但并没有覆盖setProperties()方法和getDataSource()方法。两者唯一的区别是PooledDataSourceFactory的构造函数会将其dataSource字段初始化为PooledDataSource对象。

UnpooledDataSource

javax.sql.DataSource接口在数据源模块中扮演了产品接口的角色,Mybatis提供了两个DataSource接口的实现类,分别是UnpooledDataSource和PooledDataSource,它们扮演着具体产品类的角色。
UnpooledDataSource实现了javax.sql.DataSource接口中定义的getConnection()方法及其重载方法,用于获取数据库连接。每次通过UnpooledDataSource.getConnection()方法获取数据库连接时都会创建一个新连接。UnpooledDataSource中的字段如下,每个字段都有对应的getter/setter方法:

private ClassLoader driverClassLoader; // 加载Driver类的类加载器
private Properties driverProperties; // 数据库连接驱动的相关配置
// 缓存所有已注册的数据库连接驱动
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>();

private String driver; // 数据库连接驱动的名称
private String url; // 数据库URL
private String username; // 用户名
private String password; // 密码

private Boolean autoCommit; // 是否自动提交
private Integer defaultTransactionIsolationLevel; // 事务隔离级别

static {
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
      Driver driver = drivers.nextElement();
      registeredDrivers.put(driver.getClass().getName(), driver);
    }
}

UnpooledDataSource中定义了静态代码块,在UnpooledDataSource加载时会通过该静态代码块将已在DriverManager中注册的JDBC Driver复制一份到UnpooledDataSource.registeredDrivers集合中。
UnpooledDataSource.getConnection()方法的所有重载最终会调用UnpooledDataSource.doGetConnection()方法获取数据库连接,具体实现如下:

private Connection doGetConnection(Properties properties) throws SQLException {
       initializeDriver(); // 初始化数据库驱动
       // 创建真正的数据库连接
       Connection connection = DriverManager.getConnection(url, properties);
       // 配置数据库连接的autoCommit和隔离级别
       configureConnection(connection);
       return connection;
}

UnpooledDataSource.initializelDriver()方法主要负责数据库驱动的初始化,该方法会创建配置中指定的Driver对象,并将其注册到DriverManager以及上面介绍的UnpooledDataSource.registerDrivers集合中保存。

private synchronized void initializeDriver() throws SQLException {
    if (!registeredDrivers.containsKey(driver)) { // 检测驱动是否已经注册
      Class<?> driverType;
      try {
        if (driverClassLoader != null) {
          driverType = Class.forName(driver, true, driverClassLoader);
        } else {
          driverType = Resources.classForName(driver);
        }
        // DriverManager requires the driver to be loaded via the system ClassLoader.
        // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
        Driver driverInstance = (Driver)driverType.newInstance();
        DriverManager.registerDriver(new DriverProxy(driverInstance));
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
}

UnpooledDataSource.configureConnection()方法主要完成数据库连接的一系列配置,具体实现如下:

private void configureConnection(Connection conn) throws SQLException {
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
      conn.setAutoCommit(autoCommit); // 设置事务是否自动提交
    }
    if (defaultTransactionIsolationLevel != null) {
      // 设置事务隔离级别
      conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
}

PooledDataSource

了解JDBC编程的读者知道,数据库连接的创建过程是非常耗时的,数据库能够建立的连接数也非常有限,所以在绝大多数系统中,数据库连接都是非常珍贵的资源,使用数据库连接池就显得尤为必要。使用数据库连接池会带来很多好处,例如,可以实现数据库连接的重用,提高响应速度、防止数据库连接过多造成数据库假死、避免数据库连接泄露等。
数据库连接池在初始化时,一般会创建一定数量的数据库连接并添加到连接池中备用。当程序需要使用数据库连接时,从池中请求连接;当程序不再使用该连接时,会将其返回到池中缓存,等待下次使用,而不是直接关闭。当然,数据库连接池会控制连接总数的上限以及空闲连接数的上限,如果连接池创建的总连接数已达到上限,且都以及被占用,则后续请求连接的线程会进入等待队列等待,直到有线程释放出可用的连接。如果连接池中空闲连接数较多,达到其上限,则后续返回的空闲连接不会放入池中,而是直接关闭,这样可以减少系统维护多余数据库连接的开销。
如果将总连接数的上限设置得过大,可能因连接数过多而导致数据库僵死,系统整体性能下降;如果总连接数上限过小,则无法完全发挥数据库性能,浪费数据库资源。如果将空闲连接的上限设置得过大,则会浪费系统资源维护这些空闲连接;如果空闲连接上限过小,当出现瞬间的峰值请求时,系统的快速响应能力就比较弱。所以在设置数据库连接池的这两个值时,需要进行性能测试、权衡以及一些经验。
PooledDataSource并不会直接管理java.sql.Connection对象,而是管理PooledConnection对象。在PooledConnection中封装了真正的数据库连接对象(java.sql.Connection)以及其代理对象,这里的代理对象是通过JDK动态代理产生的。PooledConnection继承了InvocationHandler接口。
PooledConnection中的核心字段如下:

private static final String CLOSE = "close";
private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };

private final int hashCode;
// 记录当前PooledConnection对象所在的PooledDataSource对象。该PooledConnection是从
// 该PooledDataSource中获取的;当调用close()方法时会将PooledConnection放回该dataSource中
private final PooledDataSource dataSource;
private final Connection realConnection; // 真正的数据库连接
private final Connection proxyConnection; // 数据库连接的代理对象
private long checkoutTimestamp; // 从连接池中取出该连接的时间戳
private long createdTimestamp; // 该连接创建的时间戳
private long lastUsedTimestamp; // 最后一次被使用的时间戳
private int connectionTypeCode; // 由数据库URL、用户名和密码计算出来的hash值,可用于标识该连接所在的连接池
// 检测当前PooledConnection是否有效,主要是为了防止程序通过close()方法将连接归还给连接池之后,依然通过该连接操作数据库
private boolean valid;

PooledConnection中提供了上述字段的getter/setter方法。PooledConnection.invoke()方法的实现,该方法是ProxyConnection这个连接代理对象的真正代理逻辑,它会对close()方法的调用进行代理,并且在调用真正数据库连接的方法之前进行检测,代码如下:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    // 如果调用close()方法,则将其重新放入连接池,而不是真正关闭数据库连接
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      return null;
    } else {
      try {
        if (!Object.class.equals(method.getDeclaringClass())) {
          // issue #579 toString() should never fail
          // throw an SQLException instead of a Runtime
          // 通过valid字段检测连接是否有效
          checkConnection();
        }
        return method.invoke(realConnection, args); // 调用真正数据库连接对象的对应方法
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
}

PoolState是用于管理PooledConnection对象状态的组件,它通过两个ArrayList集合分别管理空闲状态的连接和活跃状态的连接,定义如下:

protected PooledDataSource dataSource;
// 空闲的PooledConnection集合
protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
// 活跃的PooledConnection集合
protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
protected long requestCount = 0; // 请求数据库连接的次数
protected long accumulatedRequestTime = 0; // 获取连接的累积时间
// checkoutTime表示应用从连接池中取出连接,到归还连接这段时长,
// accumulatedCheckoutTime记录了所有连接累积的checkoutTime时长
protected long accumulatedCheckoutTime = 0;
// 当连接长时间未归还给连接池时,会被认为该连接超时,
// claimedOverdueConnectionCount记录了超时的连接个数
protected long claimedOverdueConnectionCount = 0;
// 累积超时时间
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
// 累积等待时间
protected long accumulatedWaitTime = 0;
// 等待次数
protected long hadToWaitCount = 0;
// 无效的连接数
protected long badConnectionCount = 0;

PooledDataSource中管理的真正的数据库连接对象是由PooledDataSource中封装的UnpooledDataSource对象创建的,并由PoolSize管理所有连接的状态。PooledDataSource中核心字段和功能如下:

// 通过PoolState管理连接池的状态并记录统计信息
private final PoolState state = new PoolState(this);
// 记录UnpooledDataSource对象,用于生成真实的数据库连接的对象,构造函数中会初始化该字段
private final UnpooledDataSource dataSource;

// OPTIONAL CONFIGURATION FIELDS
protected int poolMaximumActiveConnections = 10; // 最大活跃连接数
protected int poolMaximumIdleConnections = 5; // 最大空闲连接数
protected int poolMaximumCheckoutTime = 20000; // 最大checkout时长
protected int poolTimeToWait = 20000; // 在无法获取连接时,线程需要等待的时间
protected int poolMaximumLocalBadConnectionTolerance = 3; 
// 在检测一个数据库连接是否可用时,会给数据库发送一个测试SQL语句
protected String poolPingQuery = "NO PING QUERY SET"; 
protected boolean poolPingEnabled; // 是否允许发送测试语句
// poolPingConnectionsNotUsedFor毫秒未使用时,会发送一个测试SQL语句,检测连接是否正常
protected int poolPingConnectionsNotUsedFor;
// 根据数据库URL,用户名和密码生成一个hash值,该哈希值用于标志着当前的连接池,在构造函数中初始化
private int expectedConnectionTypeCode;

PooledDataSource.getConnection()方法会首先调用PooledDataSource.popConnection()方法获取PooledDataSource对象,然后通过PooledConnection.getProxyConnection()方法获取数据库连接的代理对象,popConnection()方法是PooledDataSource的核心逻辑之一,其具体逻辑如下:
PooledDataSource 1.png
PooledDataSource.popConnection()方法的具体实现如下:

private PooledConnection popConnection(String username, String password) throws SQLException {
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;
    
    while (conn == null) {
      synchronized (state) { // 同步
        if (!state.idleConnections.isEmpty()) { // 检测是否有空闲连接
          // Pool has available connection
          conn = state.idleConnections.remove(0); // 直接获取空闲连接使用
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else { // 当前连接池没有空闲连接
          // Pool does not have available connection
          // 活跃连接数没有达到最大值
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // Can create new connection
            // 创建数据库连接,并封装成PooledConnection对象
            conn = new PooledConnection(dataSource.getConnection(), this);
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else { // 活跃连接数已到最大值,则不能创建新连接
            // Cannot create new connection
            // 获取最先创建的活跃连接
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            if (longestCheckoutTime > poolMaximumCheckoutTime) { // 检测该连接是否超时
              // Can claim overdue connection
              // 对超时连接的信息进行统计
              state.claimedOverdueConnectionCount++;
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
              state.accumulatedCheckoutTime += longestCheckoutTime;
              // 将超时连接移除activeConnections集合
              state.activeConnections.remove(oldestActiveConnection);
              // 如果超时连接未提交,则自动回滚
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                try {
                  oldestActiveConnection.getRealConnection().rollback();
                } catch (SQLException e) {
                  /*
                     Just log a message for debug and continue to execute the following
                     statement like nothing happend.
                     Wrap the bad connection with a new PooledConnection, this will help
                     to not intterupt current executing thread and give current thread a
                     chance to join the next competion for another valid/good database
                     connection. At the end of this loop, bad {@link @conn} will be set as null.
                   */
                  log.debug("Bad connection. Could not roll back");
                }  
              }
              // 创建新的PooledConnection对象,但是真正的数据库连接并未创建新的
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
              conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
              // 将超时PooledConnection设置为无效
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {
              // 无空闲连接,无法创建新连接且无超时连接,则只能阻塞等待
              // Must wait
              try {
                if (!countedWait) {
                  // 统计等待次数
                  state.hadToWaitCount++;
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                }
                long wt = System.currentTimeMillis();
                // 阻塞等待
                state.wait(poolTimeToWait);
                统计累积等待时间
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
              } catch (InterruptedException e) {
                break;
              }
            }
          }
        }
        if (conn != null) {
          // ping to server and check the connection is valid or not
          if (conn.isValid()) { // 检测PooledConnection是否有效
            if (!conn.getRealConnection().getAutoCommit()) {
              conn.getRealConnection().rollback();
            }
            // 配置PooledConnection的相关属性,设置connectionTypeCode、checkoutTimestamp、lastUsedTimestamp字段的值
            conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
            conn.setCheckoutTimestamp(System.currentTimeMillis());
            conn.setLastUsedTimestamp(System.currentTimeMillis());
            state.activeConnections.add(conn); // 进行相关统计
            state.requestCount++;
            state.accumulatedRequestTime += System.currentTimeMillis() - t;
          } else {
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
            }
            state.badConnectionCount++;
            localBadConnectionCount++;
            conn = null;
            if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
              if (log.isDebugEnabled()) {
                log.debug("PooledDataSource: Could not get a good connection to the database.");
              }
              throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
            }
          }
        }
      }

}

pushConnection()方法的代码如下:

protected void pushConnection(PooledConnection conn) throws SQLException {

    synchronized (state) { // 同步
      // 从activeConnections集合中移除该PooledConnection对象
      state.activeConnections.remove(conn);
      if (conn.isValid()) { // 检测连接是否还有效
        // 检测空闲连接是否已经达到上限,以及PooledConnection是否为该连接池的连接
        if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
          state.accumulatedCheckoutTime += conn.getCheckoutTime(); // 累积checkout时长
          if (!conn.getRealConnection().getAutoCommit()) { // 回滚未绑定的事务
            conn.getRealConnection().rollback();
          }
          // 为返还连接创建新的PooledConnection对象
          PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
          state.idleConnections.add(newConn); // 添加到idleConnections集合
          newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
          newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
          conn.invalidate(); // 将原PooledConnection对象设置为无效
          if (log.isDebugEnabled()) {
            log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
          }
          state.notifyAll(); // 唤醒阻塞等待的线程
        } else { // 空闲连接数已经到上限或PooledConnection对象并不属于该连接池
          state.accumulatedCheckoutTime += conn.getCheckoutTime(); // 累积checkout时长
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          conn.getRealConnection().close(); // 关闭真正的数据库连接
          if (log.isDebugEnabled()) {
            log.debug("Closed connection " + conn.getRealHashCode() + ".");
          }
          conn.invalidate(); // 将PooledConnection对象设置为无效
        }
      } else {
        if (log.isDebugEnabled()) {
          log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
        }
        state.badConnectionCount++; // 统计无效PooledConnection对象个数
      }
    }
}

这里需要注意的是,PooledDataSource.pushConnection()方法和popConnection()方法中都调用了PooledConnection.isValid()方法来检测PooledConnection的有效性,该方法除了检测PooledConnection.valid字段的值,还会调用PooledDataSouce.pingConnection()方法尝试让数据库执行poolPingQuery字段中记录的测试SQL语句,从而检测真正的数据库连接对象是否依然可以正常使用。isValid()方法以及pingConnection()方法的代码如下:

protected boolean pingConnection(PooledConnection conn) {
    boolean result = true; // 记录ping操作是否成功
    
    try {
      result = !conn.getRealConnection().isClosed(); // 检测真正的数据库连接是否已经关闭
    } catch (SQLException e) {
      if (log.isDebugEnabled()) {
        log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
      }
      result = false;
    }
    
    if (result) {
      if (poolPingEnabled) {// 检测poolPingEnabled设置,是否运行执行测试SQL语句
        // 长时间(超过poolPingConnectionsNotUse指定的时长)未使用的连接,不需要ping操作来检测数据连接是否正常
        if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
          try {
            if (log.isDebugEnabled()) {
              log.debug("Testing connection " + conn.getRealHashCode() + " ...");
            }
            Connection realConn = conn.getRealConnection();
            Statement statement = realConn.createStatement();
            ResultSet rs = statement.executeQuery(poolPingQuery);
            rs.close();
            statement.close();
            if (!realConn.getAutoCommit()) {
              realConn.rollback();
            }
            result = true;
            if (log.isDebugEnabled()) {
              log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
            }
          } catch (Exception e) {
            log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
            try {
              conn.getRealConnection().close();
            } catch (Exception e2) {
              //ignore
            }
            result = false;
            if (log.isDebugEnabled()) {
              log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
            }
          }
        }
      }
    }
    return result;
}

最后需要注意的是PooledDataSource.forceCloseAll()方法,当修改PooledDataSource的字段时,都会调用forceCloseAll()方法将所有数据库连接关闭,同时也会将所有相应的PooledConnection对象都设置为无效,清空activeConnections和idleConnections集合。应用系统之后通过PooledDataSource.getConnection()获取连接时,会按照新的配置重新创建新的数据库连接以及相应的PooledConnection对象。forceCloseAll()方法的具体实现如下:

public void forceCloseAll() {
    synchronized (state) {
      // 更新当前连接池的标识
      expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
      for (int i = state.activeConnections.size(); i > 0; i--) { // 处理全部的活跃连接
        try {
          // 从PoolState.activeConnections集合中获取PooledConnection对象
          PooledConnection conn = state.activeConnections.remove(i - 1);
          conn.invalidate(); // 将PooledConnection对象设置为无效
    
          Connection realConn = conn.getRealConnection(); // 获取真正的数据库连接对象
          if (!realConn.getAutoCommit()) {
            realConn.rollback(); // 回滚未提交事务
          }
          realConn.close(); // 关闭真正的数据库连接
        } catch (Exception e) {
          // ignore
        }
      }
      // 处理全部idleConnections集合中的空闲连接
      for (int i = state.idleConnections.size(); i > 0; i--) {
        try {
          PooledConnection conn = state.idleConnections.remove(i - 1);
          conn.invalidate();
    
          Connection realConn = conn.getRealConnection();
          if (!realConn.getAutoCommit()) {
            realConn.rollback();
          }
          realConn.close();
        } catch (Exception e) {
          // ignore
        }
      }
    }
    if (log.isDebugEnabled()) {
      log.debug("PooledDataSource forcefully closed/removed all connections.");
    }
}

Transaction

Mybatis使用Transaction接口对数据库事务进行了抽象,Transaction接口的定义如下:

public interface Transaction {

  /**
   * Retrieve inner database connection
   * @return DataBase connection
   * @throws SQLException
   */
  Connection getConnection() throws SQLException; // 获取对应的数据库连接对象

  /**
   * Commit inner database connection.
   * @throws SQLException
   */
  void commit() throws SQLException; // 提交事务

  /**
   * Rollback inner database connection.
   * @throws SQLException
   */
  void rollback() throws SQLException; // 回滚事务

  /**
   * Close inner database connection.
   * @throws SQLException
   */
  void close() throws SQLException; // 关闭数据库连接

  /**
   * Get transaction timeout if set
   * @throws SQLException
   */
  Integer getTimeout() throws SQLException; // 获取事务超时时间
  
}

Transaction接口有JdbcTransaction、ManagedTransaction两个实现,其对象分别由JdbcTransactionFactory和ManagedTransactionFactory负责创建。
JdbcTransaction依赖于JDBCConnection控制事务的提交和回滚。JdbcTransaction中字段的含义如下:

protected Connection connection; // 事务对应的数据库连接
protected DataSource dataSource; // 数据库连接所属的连接池
protected TransactionIsolationLevel level; // 事务隔离级别
// MEMO: We are aware of the typo. See #941
protected boolean autoCommmit; // 是否自动提交

在JdbcTransaction的构造函数中会初始化除connection字段之外的其他三个字段,而connection字段会延迟初始化,它会在调用getConnection()方法通过dataSource.getConnection()方法初始化,并且同时设置autoCommit和事务隔离级别。JdbcTransaction的commit()方法和rollback()方法都会调用Connection对应的方法实现的。
TransactionFactory接口定义了配置新建TransactionFactory对象的方法,以及创建Transaction对象的方法。

public interface TransactionFactory {

  /**
   * Sets transaction factory custom properties.
   * @param props
   */
  // 配置TransactionFactory对象,一般紧跟在创建完成之后,完成对TransactionFactory的自定义配置
  void setProperties(Properties props);

  /**
   * Creates a {@link Transaction} out of an existing connection.
   * @param conn Existing database connection
   * @return Transaction
   * @since 3.1.0
   */
   // 在指定连接上创建Transaction对象
  Transaction newTransaction(Connection conn);
  
  /**
   * Creates a {@link Transaction} out of a datasource.
   * @param dataSource DataSource to take the connection from
   * @param level Desired isolation level
   * @param autoCommit Desired autocommit
   * @return Transaction
   * @since 3.1.0
   */
   // 从指定数据源中获取数据库连接,并在此连接之上创建Transaction对象
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);

}

JdbcTransactionFactory和ManagedTransactionFactory负责创建JdbcTransaction和ManagedTransaction。

binding模块

在ibatis中,查询一个Blog对象时需要调用SqlSession.queryForObject("selectBlog", blogId)方法。其中,SqlSession.queryForObject()方法会执行指定的SQL语句进行查询并返回一个结果对象,第一个参数"selectBlog"指明了具体执行的SQL语句的id,该SQL语句定义在相应的映射配置文件中。如果写错,在初始化过程中,Mybatis无法提示该错误的,而在实际调用中才会抛出异常。
Mybatis提供了binding模块用于解决上述问题。

MapperRegistry&MapperProxyFactory

MapperRegistry是Mapper接口及其对应的代理对象工厂的注册中心。Configuration是Mybatis全局性的配置对象,在Mybatis初始化过程中,所有配置信息会被解析成相应的对象并记录到Configuration对象中。MapperRegistry中字段的含义和功能如下:

// Configuration对象,Mybatis全局唯一的配置对象,其中包含了所有配置信息
private final Configuration config;
// 记录了Mapper接口与对应的MapperProxyFactory之间的关系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

在Mybatis初始化过程中会读取映射配置文件以及Mapper接口中的注解信息,并调用MapperRegistry.addMapper()方法填充MapperRegistry.knownMappers集合,该集合的key是Mapper接口对应的Class对象,value为MapperProxyFactory工厂对象,可以为Mapper接口创建代理对象。addMapper()方法如下:

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) { // 检测type是否为接口
      if (hasMapper(type)) {  // 检测是否已经加载过该接口
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // 将Mapper接口对应的Class对象和MapperProxyFactory对象添加到knownMappers集合
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
}

在需要执行某SQL语句时,会先调用MapperRegistry.getMapper()方法获取实现了Mapper接口的代理对象。session.getMapper(Class)方法得到的实际上是Mybatis通过JDK动态代理的代理对象。getMapper方法的代码如下:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 查找指定type对应的MapperProxyFactory对象
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

MapperProxyFactory主要负责创建代理对象,其中核心字段的含义和功能如下:

private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

MapperProxy

MapperFactory实现了InvocationHandler接口。MapperProxy中核心字段的含义和功能如下:

private final SqlSession sqlSession; // 记录了关联的sqlSession对象
private final Class<T> mapperInterface; // Mapper接口对应的Class对象
// 为了缓存MapperMethod对象,其中key是Mapper接口中方法对应的Method对象,value是对应的
// 需要注意的是,MapperMethod中并不记录任何状态相关的信息,所以可以在多个代理对象之间共享
private final Map<Method, MapperMethod> methodCache; 

MapperProxy.invoke()方法是代理对象执行的主要逻辑,实现如下:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // 从缓存中获取MapperMethod对象,如果缓存中没有,则创建新的MapperMethod对象并添加到缓存中
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 调用MapperMethod.execute()方法执行SQL语句
    return mapperMethod.execute(sqlSession, args);
}

MapperFactory.cacheMapperMethod()方法主要负责维护methodCache这个缓存集合,实现如下:

private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method); // 从缓存中查找MapperMethod
    if (mapperMethod == null) {
      // 创建MapperMethod对象,并添加到methodCache集合中缓存
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
}

MapperMethod

MapperMethod中封装了Mapper接口中对应方法的信息,以及对应的SQL语句的信息。MapperMethod中的字段如下:

private final SqlCommand command; // 记录了SQl语句的名称和类型
private final MethodSignature method; // Mapper接口中对应方法的相关信息

MapperMethod.execute()方法如下:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
}

缓存模块

Mybatis中的缓存是两层结构的,分为一级缓存、二级缓存,但在本质上是相同的,它们使用的都是Cache接口的实现。

Cache接口及实现

Mybatis中缓存模块相关的代码位于cache包下,其中Cache接口是缓存模块中最核心的接口,它定义了所有缓存的基本行为。Cache接口的定义如下:

public interface Cache {

  /**
   * @return The identifier of this cache
   */
  String getId(); // 该缓存对象的id

  /**
   * @param key Can be any object but usually it is a {@link CacheKey}
   * @param value The result of a select.
   */
  void putObject(Object key, Object value); //向缓存中添加数据,一般情况下,key是CacheKey,value是查询结果

  /**
   * @param key The key
   * @return The object stored in the cache.
   */
  Object getObject(Object key); // 根据指定的key,在缓存中查找对应的结果对象

  /**
   * As of 3.3.0 this method is only called during a rollback 
   * for any previous value that was missing in the cache.
   * This lets any blocking cache to release the lock that 
   * may have previously put on the key.
   * A blocking cache puts a lock when a value is null 
   * and releases it when the value is back again.
   * This way other threads will wait for the value to be 
   * available instead of hitting the database.
   *
   * 
   * @param key The key
   * @return Not used
   */
  Object removeObject(Object key); // 删除key对应的缓存项

  /**
   * Clears this cache instance
   */  
  void clear(); // 清空缓存

  /**
   * Optional. This method is not called by the core.
   * 
   * @return The number of elements stored in the cache (not its capacity).
   */
  int getSize(); // 缓存项个数
  
  /** 
   * Optional. As of 3.2.6 this method is no longer called by the core.
   *  
   * Any locking needed by the cache must be provided internally by the cache provider.
   * 
   * @return A ReadWriteLock 
   */
  ReadWriteLock getReadWriteLock(); // 获取读写锁

}