Mybatis技术内幕-基础支撑层

news2024/12/28 17:36:12

整体架构

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HackMyVM-Minimal

目录 信息收集 arp nmap nikto whatweb WEB web信息收集 gobuster 文件包含漏洞 提权 web信息收集 main方法 question_1 question_2 question_3 prize.txt 软连接 信息收集 arp ┌──(root?0x00)-[~/HackMyVM] └─# arp-scan -l Interface: eth0, type: E…

centos7.9系统安全加固

1、限制用户登陆 vim /etc/hosts.deny&#xff0c;若禁止192.168.0.158对服务器进行ssh的登陆&#xff0c;添加如下内容 sshd : 192.168.0.158 添加完毕后就生效了&#xff0c;直接用192.168.0.158访问主机&#xff0c;就无法连接了&#xff0c;显示 Connection closing...Soc…

pycharm报错Process finished with exit code -1073740791 (0xC0000409)

pycharm报错Process finished with exit code -1073740791 (0xC0000409) 各种垃圾文章&#xff08;包括chatgpt产生的垃圾文章&#xff09;&#xff0c;没有给出具体的解决办法。 解决办法就是把具体报错信息显示出来&#xff0c;然后再去查。 勾选 然后再运行就能把错误显示…

图像分割各种算子算法-可直接使用(Canny、Roberts、Sobel)

Canny算子&#xff1a; import numpy as np import cv2 as cv from matplotlib import pyplot as pltimg cv.imread("../test_1_1.png") edges cv.Canny(img, 100, 200)plt.subplot(121),plt.imshow(img,cmap gray) plt.title(Original Image), plt.xticks([]), …

vue2+swiper——实现多图轮播+层叠轮播——技能提升

今天看到同事在写轮播图&#xff0c;由于是jq的写法&#xff0c;我没有过多参与&#xff0c;我只写vue的部分。。。虽然语言不一样&#xff0c;但是用法还是要会的。下面介绍通过swiper组件来实现轮播效果。 解决步骤1&#xff1a;安装swiper npm install swiper5.4.5 我这边…

数据分享—全国分省河流水系

河流水系数据是日常研究中必备的数据之一&#xff0c;本期推文主要分享全国分省份的水系和河流数据&#xff0c;梧桐君会不定期的更新数据&#xff0c;欢迎长期订阅。 数据预览 山东省河流水系 吉林省河流水系 四川省河流水系 数据获取方式 链接&#xff1a;https://pan.baidu.…

基于阿里云向量检索 Milvus 版与 PAI 搭建高效的检索增强生成(RAG)系统

阿里云向量检索 Milvus 版现已无缝集成于阿里云 PAI 平台&#xff0c;一站式赋能用户构建高性能的检索增强生成&#xff08;RAG&#xff09;系统。您可以利用 Milvus 作为向量数据的实时存储与检索核心&#xff0c;高效结合 PAI 和 LangChain 技术栈&#xff0c;实现从理论到实…

网络基础(三)——网络层

目录 IP协议 1、基本概念 2、协议头格式 2.1、报头和载荷如何有效分离 2.2、如果超过了MAC的规定&#xff0c;IP应该如何做呢&#xff1f; 2.3、分片会有什么影响 3、网段划分 4、特殊的ip地址 5、ip地址的数量限制 6、私有ip地址和公网ip地址 7、路由 IP协议 网络…

LINUX 精通 1——2.1.1 网络io与io多路复用select/poll/epoll

LINUX 精通 1 day12 20240509 算法刷题&#xff1a; 2道高精度 耗时 107min 课程补20240430 耗时&#xff1a;99 min day 13 20240512 耗时&#xff1a;200min 课程链接地址 前言 杂 工作5-10年 够用 费曼&#xff1a;不要直接抄&#xff0c;自己写&#xff1b;不要一个…

【微服务】spring aop实现接口参数变更前后对比和日志记录

目录 一、前言 二、spring aop概述 2.1 什么是spring aop 2.2 spring aop特点 2.3 spring aop应用场景 三、spring aop处理通用日志场景 3.1 系统日志类型 3.2 微服务场景下通用日志记录解决方案 3.2.1 手动记录 3.2.2 异步队列es 3.2.3 使用过滤器或拦截器 3.2.4 使…

安全工程师面试题

安全工程师面试题安全工程师是一个非常重要的职位&#xff0c;他们负责保护公司的网络和系统免受黑客和恶意软件的攻击。如果你想成为一名安全工程师&#xff0c;那么你需要准备好面试。下面是一… 1安全工程师面试题 安全工程师是一个非常重要的职位&#xff0c;他们负责保护…

【全开源】Java俱乐部系统社区论坛商城系统源码-奔驰奥迪保时捷大众宝马等汽车俱乐部

特色功能&#xff1a; 会员中心&#xff1a;会员中心可以帮助企业更好地管理客户&#xff0c;包括设置积分商城、会员卡充值、个人汽车档案等功能&#xff0c;对不同的会员群体展开有针对性的营销&#xff0c;并维护和积累自己的粉丝群体。信息服务&#xff1a;负责定期发布新…

后端项目开发笔记

Maven打包与JDK版本不对应解决方法 我这里使用jdk8。 <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configurat…

【Docker】Ubunru下Docker的基本使用方法与常用命令总结

【Docker】docker的基本使用方法 镜像image与容器container的关系基本命令- 查看 Docker 版本- 拉取镜像- 查看系统中的镜像- 删除某个镜像- 列出当前 Docker 主机上的所有容器&#xff0c;包括正在运行的、暂停的、已停止的&#xff0c;以及未运行的容器- 列出当前 Docker 主机…

day05-面向对象内存原理和数组

day05 面向对象内存原理和数组 我们在之前已经学习过创建对象了,那么在底层中他是如何运行的。 1.对象内存图 1.1 Java 内存分配 Java 程序在运行时&#xff0c;需要在内存中分配空间。为了提高运算效率&#xff0c;就对空间进行了不同区域的划分&#xff0c;因为每一片区域…

leetcode——反转链表

206. 反转链表 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;创建三个指针n1,n2,n3&#xff0c;遍历原链表&#xff0c;通过三者之间的关系将链表反转。下面给出图示&#xff1a; 下面给出题解代码&#xff1a; typedef struct ListNode ListNode; struct List…

C++入门指南(上)

目录 ​编辑 一、祖师爷画像 二、什么是C 三、C发展史 四、C在工作领域的应用 1. 操作系统以及大型系统软件开发 2. 服务器端开发 3. 游戏开发 4. 嵌入式和物联网领域 5. 数字图像处理 6. 人工智能 7. 分布式应用 五、如何快速上手C 一、祖师爷画像 本贾尼斯特劳斯…

vmware虚拟机内删除文件后宿主机空间不释放

问题描述 linux下&#xff0c;vmware内虚拟机删除文件&#xff0c;宿主机空间不释放&#xff0c;D盘快满了 解决方法 通过vmware-toolbox进行空间回收 安装 在虚拟机内操作 yum install -y open-vm-tools 清理 在虚拟机内操作 #查看磁盘的挂载点 sudo /usr/bin/vmware…

设计模式-结构型-桥接模式-Bridge

桥接模式可以减少类的创建 矩阵类 public class Matrix {private String fileName;public Matrix(String fileName) {this.fileName fileName;}public String getFileName() {return fileName;} } 图片抽象类 public abstract class Image {protected ImageImp imp;public …

C#二维数组(矩阵)求伴随矩阵和逆矩阵

程序框架及winform窗体 窗体控件: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace Matrix {internal class Algorithm_Gallery{// <summary>/// 计算 A[p,q] 位于 [,]temp 的块辅因子…