Mybatis技术内幕-基础支撑层

整体架构

MyBatis 的整体架构分为三层, 分别是基础支持层、核心处理层和接口层。

image-20240420150957288

基础支持层

基础支持层包含整个MyBatis 的基础模块,这些模块为核心处理层的功能提供了良好的支撑。

解析器模块

XPathParser

MyBatis提供的XPathParser 类封装了XPathDocumentEntityResolver

XPathParser 中提供了一系列的eval*()方法用于解析boolean 、short、long 、int 、String 、Node
等类型的信息,它通过调用前面介绍的XPath.evaluate()方法查找指定路径的节点或属性,并进行相应的类型装换。

XPathParser.evalString()方法,其中会调用PropertyParser.parse()方法处理节点中相应的默认值

PropertyParser 中指定了是否开启使用默认值的功能以及默认的分隔符

private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
public static final String KEY_ENABLE_DEFAULT_VALUE = KEY_PREFIX + "enable-default-value";
public static final String KEY_DEFAULT_VALUE_SEPARATOR = KEY_PREFIX + "default-value-separator";

private static final String ENABLE_DEFAULT_VALUE = "false";
private static final String DEFAULT_VALUE_SEPARATOR = ":";

PropertyParser.parse()方法中会创建GenericTokenParser 解析器,井将默认值的处理委托给GenericTokenParser.parse()方法。

GenericTokenParser是一个通用的宇占位符解析器。GenericTokenParser.parse方法的会顺序查找openTokencloseToken,解析得到占位符的字面值,并将其交给TokenHandler 处理, 然后将解析结果重新拼装成字符串井返回。

public class GenericTokenParser {

  private final String openToken;
  private final String closeToken;
  private final TokenHandler handler;
}

占位符由TokenHandler接口的实现进行解析, TokenHandler 接口总共有四个实现:

  • BindingTokenParser
  • DynamicCheckerTokenParser
  • ParameterMappingTokenHandler
  • VariableTokenHandler

PropertyParser 是使用VariableTokenHandlerGenericTokenParser 配合完成占位符解析的。

VariableTokenHandler实现了TokenHandler 接口中的handleToken方法,该实现首先会按照defaultValueSeparator 宇段指定的分隔符对整个占位符切分, 得到占位符的名称和默认值,然后按照切分得到的占位符名称查找对应的值, 如果在<properties>节点下未定义相应的键值对,则将切分得到的默认值作为解析结果返回。

反射工具箱

为了简化反射操作的相关代码, MyBatis提供了专门的反射模块,该模块位于org.apache.ibatis.reflection 包中,它对常见的反射操作做了进一步封装,提供了更加简洁方便的反射API。

Reflector & ReflectorFactory

Reflector 是MyBatis 中反射模块的基础,每个Reflector 对象都对应一个类,在Reflector 中缓存了反射操作需要使用的类的元信息。

public class Reflector {

  private final Class<?> type;		//class
  private final String[] readablePropertyNames;		//可读属性,getter
  private final String[] writablePropertyNames;		//可写属性,setter。
    //key:属性,value:invoker对象
  private final Map<String, Invoker> setMethods = new HashMap<>();
     //key:属性,value:invoker对象
  private final Map<String, Invoker> getMethods = new HashMap<>();
    //setter方法的参数值类型,key:属性,value:参数值类型列表。
  private final Map<String, Class<?>> setTypes = new HashMap<>();
  private final Map<String, Class<?>> getTypes = new HashMap<>();
  private Constructor<?> defaultConstructor;
	//所有属性
  private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
}

TypeParameterResolver

Type 是所有类型的父接口,它有四个子接口和一个实现类。

image-20240420161242698

TypeParameterResolver 中通过resolveFieldType方法、resolveReturnType 方法、
resolveParamTypes方法分别解析字段类型、方法返回值类型和方法参数列表中各个参数的类型。

ObjectFactory

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

public interface ObjectFactory {

  /** 设置配置信息*/
  default void setProperties(Properties properties) {
    // NOP
  }

  /** 通过无参构造函数创建对象*/
  <T> T create(Class<T> type);

  /** 通过参数类型,选择合适的构造函数创建对象*/
  <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);

  /** 检查是否是集合类型*/
  <T> boolean isCollection(Class<T> type);

}

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

Property 工具集

反射模块中使用到的三个属性工具类,分别是PropertyTokenizerPropertyNamerPropertyCopier

orders[0].items[0].name ”这种由“ .”和“ [] ”组成的表达式是由PropertyTokenizer进行解析的。

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
  private String name;
  private final String indexedName;
  private String index;
  private final String children;
    
    public PropertyTokenizer(String fullname) {
    }
  @Override
  public PropertyTokenizer next() {
    return new PropertyTokenizer(children);
  }
}

PropertyTokenizer 的构造方法中会对传入的表达式进行分析,并初始化上述字段。

PropertyTokenizer 继承了Iterator 接口,它可以法代处理嵌套多层表达式。PropertyTokenizer.next()方法中会创建新的PropertyTokenizer 对象并解析children 宇段记录的子表达式。

orders[0].items[0].name的迭代过程如下:

image-20240420190950606

PropertyNamer 是另一个工具类,提供了静态方法帮助完成方法名到属性名的转换,以及多种检测操作。

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

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

MetaClass

MetaClass 通过ReflectorPropertyTokenizer 组合使用, 实现了对复杂的属性表达式的解析,并实现了获取指定属性描述信息的功能。

public class MetaClass {

  private final ReflectorFactory reflectorFactory;
  private final Reflector reflector;

  private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
    this.reflectorFactory = reflectorFactory;
    this.reflector = reflectorFactory.findForClass(type);
  }
}

MetaClass. findProperty() 方法只查找“.”导航的属性,并没有检测下标。

User 类中的tele.num 这个属性表达式,最终得到builder 中记录的字符串为tele.num

ObjectWrapper

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

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

  void set(PropertyTokenizer prop, Object value);

  String findProperty(String name, boolean useCamelCaseMapping);

  String[] getGetterNames();

  String[] getSetterNames();

  Class<?> getSetterType(String name);

  Class<?> getGetterType(String name);

  boolean hasSetter(String name);

  boolean hasGetter(String name);

  MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);

  boolean isCollection();

  void add(Object element);

  <E> void addAll(List<E> element);

}

image-20240420192110979

BaseWrapper 是一个实现了ObjectWrapper 接口的抽象类, 其中封装了MetaObject 对象

public abstract class BaseWrapper implements ObjectWrapper {

  protected static final Object[] NO_ARGUMENTS = new Object[0];
  protected final MetaObject metaObject;

  protected BaseWrapper(MetaObject metaObject) {
    this.metaObject = metaObject;
  }
    //调用MetaObject.getValue()方法,它会解析属性表达式井获取指定的属性
protected Object resolveCollection(PropertyTokenizer prop, Object object) {
    if ("".equals(prop.getName())) {
      return object;
    } else {
      return metaObject.getValue(prop.getName());
    }
  }
    //
  protected Object getCollectionValue(PropertyTokenizer prop, Object collection) {
    
  }
//
  protected void setCollectionValue(PropertyTokenizer prop, Object collection, Object value) {
    
  }
}

BeanWrapper 继承了BaseWrapper 抽象类,其中封装了一个JavaBean 对象以及该JavaBean类相应的MetaClass 对象,当然,还有从BaseWrapper 继承下来的、该JavaBean 对象相应的MetaObject 对象。

public class BeanWrapper extends BaseWrapper {

  private final Object object;
  private final MetaClass metaClass;

  public BeanWrapper(MetaObject metaObject, Object object) {
    super(metaObject);
    this.object = object;
    this.metaClass = MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory());
  }
}

CollectionWrapper 实现了ObjectWrapper 接口,其中封装了Collection<Object>类型的对象。

MapWrapperBaseWrapper 的另一个实现类,其中封装的是Map<String, Object> 类型对象。

MetaObject

ObjectWrapper 提供了获取/设置对象中指定的属性值、检测getter/setter 等常用功能,但是ObjectWrapper 省略了对属性表达式解析过程的介绍,而该解析过程是在MetaObject 中实现的。

public class MetaObject {
	//Java 原始bean对象
  private final Object originalObject;
    //封装了 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;
      //
    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);
    }
  }
}

MetaObjectObjectWrapper 中关于类级别的方法,例如hasGetter()hasSetter()findProperty()等方法,都是直接调用MetaClass 的对应方法实现的。其他方法都是关于对象级别的方法,这些方法都是与ObjectWrapper 配合实现。

类型转换

JDBC 数据类型与Java 语言中的数据类型并不是完全对应的,所以在PreparedStatement 为SQL 语句绑定参数时,需要从Java 类型转换成JDBC 类型,而从结果集中获取数据时,则需要从JDBC 类型转换成Java 类型。My Batis 使用类型处理器(TypeHanlder)完成上述两种转换。

在MyBatis 中使用JdbcType 这个枚举类型代表JDBC 中的数据类型,该枚举类型中定义了TYPE_CODE 字段,记录了JDBC 类型在java.sql.Types 中相应的常量编码,并通过一个静态集合codeLookup ( HashMap<Integer,JdbcType>类型〉维护了常量编码与JdbcType 之间的对应关系。

TypeHandler

MyBatis 中所有的类型转换器都继承了TypeHandler 接口。

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;

}

MyBatis 提供了BaseTypeHandler 这个抽象类,它实现了TypeHandler 接口,并继承了TypeReference 抽象类。

BaseTypeHandler 中实现了TypeHandler.setParameter ()方法和TypeHandler.getResult() 方法,这两个方法对于非空数据的处理都交给了子类实现。

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
  @Deprecated
  protected Configuration configuration;


  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      if (jdbcType == null) {
        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
      }
      try {
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "  + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
              + "Cause: " + e, e);
      }
    } else {
      try {
        setNonNullParameter(ps, i, parameter, jdbcType);
      } catch (Exception e) {
        throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
              + "Try setting a different JdbcType for this parameter or a different configuration property. "
              + "Cause: " + e, e);
      }
    }
  }

  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    try {
      return getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
    }
  }

  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    try {
      return getNullableResult(rs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + e, e);
    }
  }

  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    try {
      return getNullableResult(cs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + e, e);
    }
  }

  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
   */
  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

}

BaseTypeHandler 的实现类比较多,但大多是直接调用PreparedStatementResultSetCallableStatement 的对应方法,实现比较简单。

public class IntegerTypeHandler extends BaseTypeHandler<Integer> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setInt(i, parameter);
  }

  @Override
  public Integer getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    int result = rs.getInt(columnName);
    return result == 0 && rs.wasNull() ? null : result;
  }

  @Override
  public Integer getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    int result = rs.getInt(columnIndex);
    return result == 0 && rs.wasNull() ? null : result;
  }

  @Override
  public Integer getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    int result = cs.getInt(columnIndex);
    return result == 0 && cs.wasNull() ? null : result;
  }
}

TypeHandlerRegistry

在MyBatis 初始化过程中,会为所有己知的TypeHandler 创建对象,并实现注册到TypeHandlerRegistry 中,由TypeHandlerRegister负责管理这些TypeHandler 对象。

public final class TypeHandlerRegistry {
//记录JdbcType 与TypeHandler 之间的对应关系,其中JdbcType 是一个枚举类型,它定义对应的JDBC 类型
//该集合主要用于从结果集读取数据时,将数据从Jdbc 类型转换成Java 类型
  private final Map<JdbcType, TypeHandler<?>>  jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
    //记录了Java 类型向指定JdbcType 转换时,需妥使用的TypeHandler 对象。例如: Java 类型中的String 可能转换成数据库的char 、varchar 等多种类型,所以存在一对多关系
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
    
  private final TypeHandler<Object> unknownTypeHandler = new UnknownTypeHandler(this);
  //记录了全部TypeHandler 的类型以及该类型相应的T ypeHandler 对象
  private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
//空TypeHandler 集合的标识
  private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();

  private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
    //注册 TypeHandler,根据 @MappedTypes
    public <T> void register(TypeHandler<T> typeHandler){}
    //
     public void register(String packageName) {}
    //
    private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    if (ParamMap.class.equals(type)) {
      return null;
    }
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
      handler = jdbcHandlerMap.get(jdbcType);
      if (handler == null) {
        handler = jdbcHandlerMap.get(null);
      }
      if (handler == null) {
        // #591
        handler = pickSoleHandler(jdbcHandlerMap);
      }
    }
    // type drives generics here
    return (TypeHandler<T>) handler;
  }
}

注册TypeHandler 对象

TypeHandlerRegistry.register()方法实现了注册TypeHandler 对象的功能。

register ()方法重载中会尝试读取TypeHandler 类中定义的@MappedTypes注解和@MappedJdbcTypes 注解,@MappedTypes 注解用于指明该TypeHandler 实现类能够处理的Java 类型的集合,@MappedJdbcTypes 注解用于指明该TypeHandler 实现类能够处理的JDBC类型集合。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MappedTypes {
  Class<?>[] value();
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MappedJdbcTypes {
  JdbcType[] value();
  boolean includeNullJdbcType() default false;
}

TypeHandlerRegister除了提供注册单个TypeHandlerregister() 重载,还可以扫描整个包下的TypeHandler 接口实现类,并将完成这些TypeHandler 实现类的注册。

查找TypeHandler

TypeAliasRegistry

MyBatis 将SQL 语句中别名的概念进行了延伸和扩展, MyBatis可以为一个类添加一个别名,之后就可以通过别名引用该类。

MyBatis 通过TypeAliasRegister类完成别名注册和管理的功能, TypeAliasRegistry 的结构比较简单,它通过TYPE_ALIASES 字段(Map<String, Class <?>>类型)管理别名与Java 类型之间的对应关系,通过TypeAliasRegistry.registerAlias()方法完成注册别名.

TypeAliasRegistry的构造方法中,默认为Java 的基本类型及其数组类型、基本类型的封装类及其数组类型、Date 、BigDecimalBiglntegerMapHashMap 、List 、ArrayList、Collection 、Iterator 、ResultSet 等类型添加了别名。

public class TypeAliasRegistry {

 private final Map<String, Class<?>> typeAliases = new HashMap<>();
    //查找指定包下的superType 类型类
 public void registerAliases(String packageName, Class<?> superType) {}
    //
 public void registerAliases(String packageName) {
    registerAliases(packageName, Object.class);
  }
    
    public void registerAlias(Class<?> type) {
        //简单类名,
    String alias = type.getSimpleName();
        //有Alias注解,则使用注解中的值。
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      alias = aliasAnnotation.value();
    }
    registerAlias(alias, type);
  }
}

资源加载

ClassloaderWrapper

在MyBatis 的IO包中封装了ClassLoader 以及读取资源文件的相关API。在IO 包中提供的ClassLoaderWrapper 是一个ClassLoader 的包装器,其中包含了多个ClassLoader 对象。通过调整多个类加载器的使用顺序, ClassLoaderWrapper 可以确保返回给系统使用的是正确的类加载器。使用ClassLoaderWrapper 就如同使用一个ClassLoader 对象,ClassLoaderWrapper 会按照指定的顺序依次检测其中封装的ClassLoader 对象,并从中选取第一个可用的ClassLoader 完成相关功能。

ResolverUtil

ResolverUtil 可以根据指定的条件查找指定包下的类,其中使用的条件由Test 接口表示。ResolverUtil 中使用classLoader 字段( ClassLoader 类型)记录了当前使用的类加载器,默认情况下,使用的是当前线程上下文绑定的ClassLoader ,我们可以通过setClassLoader()方法修改使用类加载器。

MyBatis 提供了两个常用的Test 接口实现,分别是IsAAnnotatedWithIsA 用于检测类是否继承了指定的类或接口, AnnotatedWith 用于检测类是否添加了指定的注解。也可以自己实现Test 接口,实现指定条件的检测。

public class ResolverUtil<T> {
    
      public interface Test {
    boolean matches(Class<?> type);
  }
    
 public static class IsA implements Test {
    private Class<?> parent;
  Returns true if type is assignable to the parent type supplied in the constructor. */
    @Override
    public boolean matches(Class<?> type) {
      return type != null && parent.isAssignableFrom(type);
    }
  }
    
 public static class AnnotatedWith implements Test {
    private Class<? extends Annotation> annotation;
    @Override
    public boolean matches(Class<?> type) {
      return type != null && type.isAnnotationPresent(annotation);
    }
 }
    
}

DataSource

MyBatis 提供了两个javax.sql.DataSource 接口实现,分别是PooledDataSourceUnpooledDataSource 。Mybatis 使用不同的DataSourceFactory 接口实现创建不同类型的DataSource

DataSourceFactory

DataSourceFactory 接口扮演工厂接口的角色。UnpooledDataSourceFactoryPooledDataSourceFactory 则扮演着具体工厂类的角色。

binding 模块

SqlSession.queryForObject方法的第一个参数是 SQL id,如果不存在,会在执行SqlSession.queryForObject才会抛出异常。MyBatis 提供了binding 模块用于解决上述问题,定义一个接口(Mapper接口),不需要继承任何其他接口,而且开发人员不需要提供该接口的实现。

Mapper 接口中定义了SQL 语句对应的方法,这些方法在MyBatis 初始化过程中会与映射配置文件中定义的SQL 语句相关联。如果存在无法关联的SQL 语句,在MyBatis 的初始化节点就会抛出异常。我们可以调用Mapper 接口中的方法执行相应的SQL 语句,这样编译器就可以帮助我们提早发现上述问题。

public interface BlogMapper {
	 Blog selectBlog (int i); //在映射配置文件中存在一个< select>节点, id 为” selectBlog ”
}

//首先,获取BlogMapper 对应的代理对象
BlogMapper mapper= session.getMapper(BlogMapper.class);
//调用Mapper 接口中定义的方法执行对应的SQL 语句
Blog blog = mapper.selectBlog(l) ;

MapperRegistry & MapperProxyFactory

MapperRegistryMapper 接口及其对应的代理对象工厂的注册中心。Configuration 是MyBatis 全局性的配置对象,在MyBatis 初始化的过程中,所有配置信息会被解析成相应的对象并记录到Configuration 对象中,Configuration.mapperRegistry字段,它记录当前使用的MapperRegistry 对象。

public class MapperRegistry {
//Configuration 对象, MyBatis 全局唯一的配置对象,其中包含了所有配置信息
  private final Configuration config;
//记录了Mapper 接口与对应MapperProxyFactory 之间的关系
//key 是Mapper 接口对应的Class 对象, value 为MapperProxyFactory 工厂对象,可以为Mapper 接口创建代理对象
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }
}

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

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    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 主要负责创建代理对象。

public class MapperProxyFactory<T> {
//当前MapperProxyFactory 对象可以创建实现了MapperInterface 接口的代理对象,例如BlogMapper
  private final Class<T> mapperInterface;
    //缓存, key 是Mapperinterface 接口中某方法对应的Method对象, value 是对应的MapperMethod 对象
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
     //创建MapperProxy 对象,每次调用都会创建新的MapperProxy 对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

MapperProxy

MapperProxy 实现了lnvocationHandler 接口,的实现是代理对象的核心逻辑。

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
      | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  private static final Constructor<Lookup> lookupConstructor;
  private static final Method privateLookupInMethod;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

    
  static {
    Method privateLookupIn;
    try {
      privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
    } catch (NoSuchMethodException e) {
      privateLookupIn = null;
    }
    privateLookupInMethod = privateLookupIn;

    Constructor<Lookup> lookup = null;
    if (privateLookupInMethod == null) {
      // JDK 1.8
      try {
        lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
        lookup.setAccessible(true);
      } catch (NoSuchMethodException e) {
        throw new IllegalStateException(
            "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
            e);
      } catch (Throwable t) {
        lookup = null;
      }
    }
    lookupConstructor = lookup;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
   //如采目标方法继承自Object ,则直接调用目标方法
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {
          //针对Java7 以上版本对动态类型语言的支持
        if (privateLookupInMethod == null) {
          return invokeDefaultMethodJava8(proxy, method, args);
        } else {
          return invokeDefaultMethodJava9(proxy, method, args);
        }
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //从缓存中获取MapperMethod 对象,如采缓存中没有,则创建新的MapperMethod 对象并添加到缓存中
    final MapperMethod mapperMethod = cachedMapperMethod(method);
      //执行SQL 语句
    return mapperMethod.execute(sqlSession, args);
  }
    
  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method,
        k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

}

MapperMethod

MapperMethod 中封装了Mapper 接口中对应方法的信息,以及对应SQL 语句的信息。可以将MapperMethod 看作连接Mapper 接口以及映射配置文件中定义的SQL 语句的桥梁

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

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
    
      public static class SqlCommand {
		//记录了SQL 语句的名称,
    	private final String name;
          //记录了SQL 语句的类型。SqlCommandType 是枚举类型,有效取值为UNKNOWN 、INSERT 、UPDATE 、DELETE 、SELECT 、FLUSH 。
    	private final SqlCommandType type;
      }
    
  public static class MethodSignature {

    private final boolean returnsMany;
    private final boolean returnsMap;
    private final boolean returnsVoid;
    private final boolean returnsCursor;
    private final boolean returnsOptional;
    private final Class<?> returnType;
    private final String mapKey;
    private final Integer resultHandlerIndex;
    private final Integer rowBoundsIndex;
    private final ParamNameResolver paramNameResolver;
  }
}
ParamNameResolver

MethodSignature 中, 会使用ParamNameResolver 处理Mapper 接口中定义的方法的参数列表。ParamNameResolver 使用name 字段( SortedMap<Integer, String>类型)记录了参数在参数列表中的位置索引参数名称之间的对应关系,其中key 表示参数在参数列表中的索引位置,value 表示参数名称,参数名称可以通过@Param 注解指定,如果没有指定@Param 注解,则使用参数索引作作为其名称。如果参数列表中包含RowBounds 类型或ResultHandler 类型的参数,则这两种类型的参数并不会被记录到name 集合中,这就会导致参数的索引与名称不一致,例如, method(int a, RowBounds rb, int b)方法对应的names 集合为 { {0,"0"},{2,"1"}}

image-20240421071850643

public class ParamNameResolver {

  private static final String GENERIC_NAME_PREFIX = "param";

  /**
  aMethod(@Param("M") int a, @Param("N") int b)  -> {{0, "M"}, {1, "N"}}
  aMethod(int a, int b) ->  {{0, "0"}, {1, "1"}}
  aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}
   
   */
  private final SortedMap<Integer, String> names;

  private boolean hasParamAnnotation;
}
MethodSignature

MethodSignature 也是MapperMethod 中定义的内部类,其中封装了Mapper 接口中定义的方法的相关信息。

  public static class MethodSignature {

    private final boolean returnsMany;//返回值类型是否为Collection 类型或是数组类型
    private final boolean returnsMap;//返回值类型是否为Map 类型
    private final boolean returnsVoid;//返回值类型是否为VO 工d
    private final boolean returnsCursor;//返回值是否为Cursor 类型
    private final boolean returnsOptional;//
    private final Class<?> returnType;//返回值类型
    private final String mapKey;//如果返回值类型是Map ,则该字段记录了作为key 的列名
    private final Integer resultHandlerIndex;//用来标记该方法参数列表中ResultHandler 类型参数的位置
    private final Integer rowBoundsIndex;//用来标记该方法参数列表中RowBounds 类型参数的位置
    private final ParamNameResolver paramNameResolver;//
  }
execute()方法

MapperMethod 中最核心的方法是execute()方法,它会根据SQL 语句的类型调用SqISession 对应的方法完成数据库操作.


  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        //使用ParamNameResolver处理args[]数组(用户传入的实参列表),将用户传入的实参与指定参数名称关联起来
        Object param = method.convertArgsToSqlCommandParam(args);
        //会根据method 字段中记录的方法的返回值类型对结果进行转换
        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);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        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;
  }

当执行INSERTUPDATEDELETE 类型的SQL 语句时,其执行结果都需要经过MapperMethod.rowCountResult方法处理。SqISession 中的insert等方法返回的是int 值,rowCountResult方法会将该int 值转换成Mapper 接口中对应方法的返回值。

如果Mapper 接口中定义的方法准备使用ResultHandler 处理查询结果集,则通过MapperMethod.executeWithResultHandler方法处理。

缓存模块

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

附录

中文资料网站:https://mybatis.net.cn/index.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/617022.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud使用Nacos作为配置中心实现动态数据源切换

一、Nacos-Server 了解Nacos可以直接阅读官方文档 使用Nacos&#xff0c;我们需要有Nacos-Server&#xff0c;此处就不使用官方提供的release版本了&#xff0c;而是自己编译&#xff0c;因为本来就是Java开发的&#xff0c;所以对于Javaer来说也没啥难度&#xff01; git c…

记nrm管理仓库以及发布npm包

前言 记一次在公司创建私有库以及发布npm包&#xff0c;留下个脚印 一、nrm是什么&#xff1f; nrm是 npm 镜像源管理工具&#xff0c;用于快速地在不同的 npm 源之间切换。 二、使用步骤 1.全局安装nrm 代码如下&#xff08;示例&#xff09;&#xff1a; npm install -…

Git系列:git diff使用技巧

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

【智能算法】鹭鹰优化算法(SBOA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;Y Fu受到自然界中鹭鹰生存行为启发&#xff0c;提出了鹭鹰优化算法&#xff08;Secretary Bird Optimization Algorithm, SBOA&#xff09;。 2.算法原理 2.1算法思想…

Elasticsearch查看集群信息,设置ES密码,Kibana部署

Elasticsearch查看集群信息&#xff0c;设置ES密码&#xff0c;Kibana部署 查看集群信息查看节点信息查看集群健康状态查看分片信息查看其他集群信息 Kibana部署安装设置ES密码 查看集群信息 查看节点信息 curl http://127.0.0.1:9200/_cat/nodes?v 参数说明&#xff1a; ip…

OpenGL导入的纹理图片错位

在OpenGL中导入图片的纹理照片的函数为 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, p_w, p_h, 0, GL_BGR, GL_UNSIGNED_BYTE, pic_data);其中p_w, p_h为图片的宽和高&#xff0c;pic_data为指向图片存储空间的的地址(unsigned char *类型) 在OpenGL中图片默认是4字节对齐的&…

Web开发小知识点(二)

1.关于取余 我在Dart语言里&#xff08;flutter项目&#xff09; int checkNum (10 - 29) % 10; 那么checkNum等于1 但是在Vue项目里 const checkNum (10 - 29) % 10;却等于-9 语言的特性不同&#xff0c;导致结果也不同&#xff0c;如果要想和Dart保持一致&#xff0c;…

vue3.0(六) toRef,toValue,toRefs和toRow,markRaw

文章目录 toReftoValuetoRefstoRowmarkRawtoRef和toRefs的区别toRaw 和markRaw的用处 toRef toRef 函数可以将一个响应式对象的属性转换为一个独立的 ref 对象。返回的是一个指向源对象属性的 ref 引用&#xff0c;任何对该引用的修改都会同步到源对象属性上。使用 toRef 时需…

[蓝桥杯 2021 国 ABC] 123(java)——前缀和,思维

目录 题目 解析 代码 这么久了&#xff0c;我终于能不看别人代码完整写出来了&#xff0c;呜呜呜。虽然过程也是很曲折。 题目 解析 这个题&#xff0c;找其中数列的规律&#xff0c;1,1,2,1,2,3,1,2,3,4&#xff0c;...&#xff0c;因此我们把拆分成行列&#xff0c;如下…

Java并发处理

Java并发处理 问题描述:项目中业务编号出现重复编号 生成编号规则&#xff1a;获取数据库表最大值&#xff0c;然后再做1处理&#xff0c;即为新编号&#xff08;因为起始值是不固定的&#xff0c;还存在‘字符数据’格式&#xff0c;做了字典项可配置&#xff0c;所以不能直…

Ai 一键美术绘画文章,蓝海项目,流量巨大,盈利成效显著

今天我要向大家介绍一个全新的蓝海项目&#xff0c;那就是AI一键美术绘画文章。这个项目打破了传统的思维模式&#xff0c;更加吸引人的眼球&#xff0c;已经在各大网站上引发了大量的关注&#xff0c;轻松在抖音上热门也变得简单易行且稳定。 下载 地 址 &#xff1a; laoa…

PopClip for Mac 激活版:让文本处理更高效

还在为繁琐的文本处理而烦恼吗&#xff1f;PopClip for Mac来帮您解决&#xff01;这款神器般的文本处理工具&#xff0c;能让您轻松应对各种文本处理任务。无论是写作、编程还是日常办公&#xff0c;PopClip for Mac都能助您一臂之力&#xff0c;让您的文本处理更高效、更便捷…

【基础绘图】 09.小提琴图

效果图&#xff1a; 主要步骤&#xff1a; 1. 数据准备&#xff1a;生成随机数组 2. 数据处理&#xff1a;计算四分位数、中位数、均值、最大最小值 3. 图像绘制&#xff1a;绘制小提琴图 详细代码&#xff1a;着急的直接拖到最后有完整代码 步骤一&#xff1a;导入库包及…

动态规划----股票买卖问题(详解)

目录 一.买卖股票的最佳时机&#xff1a; 二.买卖股票的最佳时机含冷冻期&#xff1a; 三.买卖股票的最佳时期含⼿续费&#xff1a; 四.买卖股票的最佳时机III: 五.买卖股票的最佳时机IV: 买卖股票的最佳时机问题介绍&#xff1a;动态规划买卖股票的最佳时机是一个经典的…

排序2——冒泡排序,快速排序(3种递归方式+3种非递归方式)

目录 1.交换排序 2.冒泡排序 2.1基本思路 1.1.2复杂度分析 3.快速排序 3.1基本思想 3.2Hoare版本&#xff08;最初的&#xff09; 3.2.1缺点 3.2.2优化 第一种——随机选key 第二种——三数取中 第三种——小区间优化 3.3挖坑版本&#xff08;更好理解&#xff09…

【谷粒商城】01-环境准备

1.下载和安装VirtualBox 地址&#xff1a;https://www.virtualbox.org/wiki/Downloads 傻瓜式安装VirtualBox 2.下载和安装Vagrant官方镜像 地址&#xff1a;https://app.vagrantup.com/boxes/search 傻瓜式安装 验证是否安装成功 打开CMD,输入vagrant命令&#xff0c;是否…

pyqt5将ui文件转为python文件

在pyqt5中使用 pyuic将ui文件转为py文件&#xff1a; 例如&#xff1a;将home.ui文件转为vio_detect.py文件&#xff0c;所需命令如下&#xff1a; pyuic5 -x home.ui -o vio_detect.py

【Linux】基于 Jenkins+shell 实现更新服务所需文件 -->两种方式:ssh/Ansible

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

STL-Hashtable

hashtable hashtable是通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系&#xff0c;这样在查找的时候就可以很快的找到该元素。 哈希函数 哈希函数的定义域必须包括需要存储的全部关键码&#xff0c;而如果散列表允许有m个地址时&#xff0c…

C++内存管理(1)

目录 1.new用法说明 2.new/delete在栈里面的运用 3.operator new/operator delete函数 4.构造函数的显式调用 5.malloc&&new&&free&&delete区别 1.new用法说明 &#xff08;1&#xff09;在C语言阶段&#xff0c;我们无论是为数组开辟空间&#x…