MyBatis04-"일반 소스 코드 가이드: MyBatis 소스 코드에 대한 자세한 설명" 노트-구성 분석

이 일련의 기사는 "일반 소스 코드 가이드: MyBatis 소스 코드에 대한 자세한 설명" 책의 내용과 요약
입니다. 이 책은 MyBatis-3.5.2 버전을 기반으로 합니다. 책의 저자는 Brother Yi 입니다 . 링크는 다음과 같습니다. CSDN의 이 형제의 웨이보. 그런데 내가 읽은 모든 기사 중에서 이 책을 간략하게 소개한 기사는 딱 한 권뿐이었다. 책의 매력을 너무 많이 드러내지는 않습니다. 다음으로 학습 요약을 기록하겠습니다. 작성자가 제가 저작권을 침해했다고 생각하시면 저에게 연락해 삭제해 주시길 바랍니다.학습 자료를 제공해 주신 이 형제님에게 다시 한 번 감사드립니다. 이 설명은 전체 기사 시리즈에 첨부됩니다. 독창성을 존중합니다 . 나는 WeChat Reading에서 개정된 책을 구입했습니다.
저작권 설명: 이 기사는 CSDN 블로거 "Architect Yi Ge"의 원본 기사이며 CC 4.0 BY-SA 저작권 계약을 따릅니다. 재인쇄할 때 원본 소스 링크와 이 설명을 첨부하세요.
원본 링크: https://blog.csdn.net/onlinedct/article/details/107306041

1. 개요

MyBatis의 구성 파일은 두 가지 범주로 나뉩니다.

  1. MyBatis의 기본 구성 정보가 포함된 핵심 구성 파일입니다. 파일이 하나뿐입니다.
  2. Java 객체와 데이터베이스 속성, 데이터베이스 연산문 등의 매핑 관계를 설정하는 매핑 파일이다. 여러 개의 파일이 있을 수 있습니다.

데이터베이스 작업을 수행하기 전에 위 두 가지 유형의 파일에 대한 구문 분석, 변환 및 저장을 완료하십시오. 클래스 관점에서 구성 구문 분석과 관련된 클래스는 다음과 같이 나눌 수 있습니다.

  • Parser 클래스: 구성 파싱 기능을 제공하고 복사를 통해 구성 정보 추출 및 변환을 완료합니다. XMLConfigBuilder, XMLMapperBuilder가 있습니다.
  • 파싱 ​​엔터티 클래스: 구성 저장 기능을 제공합니다. 클래스의 구조는 구성 정보에 해당하며 구성 정보는 결국 구문 분석된 엔터티 클래스의 속성에 대한 오류를 보고합니다. 구성 클래스, 환경 클래스, DataSource 클래스 등

2.바인딩 패키지

바인딩 패키지는 주로 Java 메소드와 SQL 문 간의 바인딩 관계를 처리하는 데 사용되는 패키지입니다.
바인딩 패키지에는 두 가지 기능이 있습니다.

  1. 매핑 인터페이스의 추상 메서드와 데이터베이스 작업 노드 간의 연결을 유지합니다.
  2. 매핑 인터페이스의 추상 메서드에 해당하는 데이터베이스 작업에 액세스합니다.

2.1 데이터베이스 작업에 대한 접근

매핑 인터페이스의 추상 메서드에 해당하는 데이터베이스 작업에 액세스하는 것은 리플렉션을 기반으로 하는 동적 프록시에 의해 구현됩니다.
여기에 이미지 설명을 삽입하세요.

2.1.1 데이터베이스 작업의 방법화

데이터베이스 작업을 추상 메서드에 연결하려면 먼저 데이터베이스 작업 노드를 메서드로 변환해야 합니다. MapperMethod 개체는 데이터베이스 작업의 변환된 메서드를 나타냅니다. 각 MapperMethod 개체는 데이터베이스 작업 노드에 해당하며 MapperMethod 인스턴스의 실행 메서드를 호출하여 노드의 SQL 문을 시작할 수 있습니다.

MapperMethod 클래스에는 두 가지 중요한 내부 클래스에 해당하는 두 가지 특성이 있습니다.

  • MethodSignature 클래스: 특정 메소드의 시그니처
  public static class MethodSignature {
    
    

    // 返回类型是否为集合类型
    private final boolean returnsMany;
    // 返回类型是否是map
    private final boolean returnsMap;
    // 返回类型是否是空
    private final boolean returnsVoid;
    // 返回类型是否是cursor类型
    private final boolean returnsCursor;
    // 返回类型是否是optional类型
    private final boolean returnsOptional;
    // 返回类型
    private final Class<?> returnType;
    // 如果返回为map,这里记录所有的map的key
    private final String mapKey;
    // resultHandler参数的位置
    private final Integer resultHandlerIndex;
    // rowBounds参数的位置
    private final Integer rowBoundsIndex;
    // 引用参数名称解析器
    private final ParamNameResolver paramNameResolver;
}

MethodSignature의 속성은 메서드의 세부 정보를 자세히 설명합니다.

  • SqlCommand 클래스: SQL 문
public static class SqlCommand {
    
    
  // SQL语句的名称
  private final String name;
  // SQL语句的种类,一共分为以下六种:增、删、改、查、清缓存、未知
  private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
    
    
      // 方法名称
      final String methodName = method.getName();
      // 方法所在的类。可能是mapperInterface,也可能是mapperInterface的子类
      final Class<?> declaringClass = method.getDeclaringClass();
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
    
    
        if (method.getAnnotation(Flush.class) != null) {
    
    
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
    
    
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
    
    
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
    
    
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

    public String getName() {
    
    
      return name;
    }

    public SqlCommandType getType() {
    
    
      return type;
    }

    /**
     * 找出指定接口指定方法对应的MappedStatement对象
     * @param mapperInterface 映射接口
     * @param methodName 映射接口中具体操作方法名
     * @param declaringClass 操作方法所在的类。一般是映射接口本身,也可能是映射接口的子类
     * @param configuration 配置信息
     * @return MappedStatement对象
     */
    private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
    
    
      // 数据库操作语句的编号是:接口名.方法名
      String statementId = mapperInterface.getName() + "." + methodName;
      // configuration保存了解析后的所有操作语句,去查找该语句
      if (configuration.hasStatement(statementId)) {
    
    
        // 从configuration中找到了对应的语句,返回
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {
    
    
        // 说明递归调用已经到终点,但是仍然没有找到匹配的结果
        return null;
      }
      // 从方法的定义类开始,沿着父类向上寻找。找到接口类时停止
      for (Class<?> superInterface : mapperInterface.getInterfaces()) {
    
    
        if (declaringClass.isAssignableFrom(superInterface)) {
    
    
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
              declaringClass, configuration);
          if (ms != null) {
    
    
            return ms;
          }
        }
      }
      return null;
    }
  }

}

SqlCommand의 구성 방법은 주로 들어오는 매개변수에 따라 이름 및 유형 필드 할당을 완료하는 것이며,solveMappedStatement 하위 방법은 모든 것의 핵심입니다. ResolveMappedStatement 하위 메소드는 MappedStatement 객체를 쿼리하고 MappedStatement는 데이터베이스 작업 명령문과 완전히 일치하기 때문입니다.

여기에 이미지 설명을 삽입하세요.
따라서 MapperMethod 객체의 실행 메소드를 호출하기만 하면 특정 데이터베이스 작업이 트리거될 수 있으며, 데이터베이스 작업이 메소드로 변환됩니다.

/**
   * 执行映射接口中的方法
   * @param sqlSession sqlSession接口的实例,通过它可以进行数据库的操作
   * @param args 执行接口方法时传入的参数
   * @return 数据库操作结果
   */
  public Object execute(SqlSession sqlSession, Object[] args) {
    
    
    Object result;
    switch (command.getType()) {
    
     // 根据SQL语句类型,执行不同操作
      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: {
    
     // 如果是删除语句MappedStatement
        // 将参数顺序与实参对应好
        Object param = method.convertArgsToSqlCommandParam(args);
        // 执行操作并返回结果
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT: // 如果是查询语句
        if (method.returnsVoid() && method.hasResultHandler()) {
    
     // 方法返回值为void,且有结果处理器
          // 使用结果处理器执行查询
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
    
     // 多条结果查询
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
    
     // Map结果查询
          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()) {
    
    
      // 查询结果为null,但返回类型为基本类型。因此返回变量无法接收查询结果,抛出异常。
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

MapperMethod 클래스의 실행 메소드 소스 코드입니다. 실행 메소드는 자체 SQL 문의 유형에 따라 다양한 데이터베이스 작업을 트리거하는 것을 볼 수 있습니다. MapperMethod 클래스의 도움으로 Java 매핑 인터페이스에 대한 호출을 MapperMethod 개체의 실행 메서드에 대한 호출로 변환할 수 있는 한 Java 매핑 인터페이스를 호출할 때 지정된 데이터베이스 작업을 완료할 수 있습니다.

MapperMethod 클래스에는 내부 클래스 ParamMap도 있습니다. ParamMap 내부 클래스는 매개변수를 저장하는 데 사용됩니다. HashMap의 하위 클래스이지만 HashMap보다 더 엄격합니다. 존재하지 않는 키 값을 얻으려고 하면 직접 예외가 발생합니다. 데이터베이스 작업에서 존재하지 않는 입력 매개변수를 참조하면 이러한 오류를 해결할 수 없기 때문입니다.

  public static class ParamMap<V> extends HashMap<String, V> {
    
    

    private static final long serialVersionUID = -2212268410512043556L;

    @Override
    public V get(Object key) {
    
    
      if (!super.containsKey(key)) {
    
    
        throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
      }
      return super.get(key);
    }
  }

2.1.2 데이터베이스 운영 방법에 대한 접근

이전 섹션에서는 데이터베이스 작업을 메서드(여기서는 MapperMethod 개체의 실행 메서드를 나타냄)로 변환했는데 이 메서드를 어떻게 호출할 수 있을까요?
"List<User>queryUserBySchoolName(사용자 사용자)"와 같은 매핑 인터페이스의 메서드를 호출하면 Java는 인터페이스의 구현 클래스에서 해당 메서드를 찾아서 실행합니다. 매핑 인터페이스에는 구현 클래스가 없으므로 매핑 인터페이스에서 메서드를 호출하면 오류가 발생합니다. 대신 MapperMethod 클래스에서 실행 메서드를 어떻게 호출할 수 있습니까?

위 작업에는 MapperProxy 클래스의 도움이 필요합니다. 이 클래스는 매핑 인터페이스에 대한 메서드 호출을 동적 프록시 기반의 MapperMethod 개체의 실행 메서드에 대한 호출로 전송하여 데이터베이스 작업을 구현합니다. MapperProxy는 InvocationHandler 인터페이스를 상속하며 동적 프록시 클래스입니다. 즉, 프록시된 개체 대신 인스턴스가 사용되면 프록시된 개체에 대한 메서드 호출이 MapperProxy의 호출 메서드로 전송됩니다.

public class MapperProxy<T> implements InvocationHandler, Serializable {
    
    
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    try {
    
    
      if (Object.class.equals(method.getDeclaringClass())) {
    
     // 继承自Object的方法
        // 直接执行原有方法
        return method.invoke(this, args);
      } else if (method.isDefault()) {
    
     // 默认方法
        // 执行默认方法
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
    
    
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // 找对对应的MapperMethod对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 调用MapperMethod中的execute方法
    return mapperMethod.execute(sqlSession, args);
  }
}

MapperProxyFactory는 MapperProxy의 생산 팩토리이며 newInstance 코어 메소드는 MapperProxy 객체를 생성합니다. 이 시점에서 우리는 해당 MapperProxy 개체가 매핑 인터페이스의 구현으로 사용되는 한 매핑 인터페이스에 대한 데이터베이스 작업에 액세스하는 기능이 완전히 실현될 수 있다는 것을 알고 있습니다.

2.1.3 추상 메서드는 데이터베이스 작업 노드와 연결됩니다.

매핑 인터페이스의 추상 메서드는 액세스하려는 MapperMethod 개체를 어떻게 결정합니까?
MyBatis는 이 문제를 두 단계로 해결합니다.

  • 첫 번째 단계에서 MyBatis는 매핑 인터페이스를 MapperProxyFactory와 연결합니다. 이 연관은 MapperRegistry 클래스의 KnownMappers 속성에서 유지됩니다. KnownMappers는 키가 매핑 인터페이스이고 값이 해당 MapperProxyFactory 개체인 HashMap입니다.
public class MapperRegistry {
    
    

  private final Configuration config;
  // 已知的所有映射
  // key:mapperInterface,即dao的数据库接口,不是方法
  // value:MapperProxyFactory,即映射器代理工厂
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  /**
   * 找到指定映射接口的映射文件,并根据映射文件信息为该映射接口生成一个代理实现
   * @param type 映射接口
   * @param sqlSession sqlSession
   * @param <T> 映射接口类型
   * @return 代理实现对象
   */
  @SuppressWarnings("unchecked")
  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 {
    
    
      // 通过mapperProxyFactory给出对应代理器的实例
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
    
    
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

MapperProxyFactory의 생성자에는 매핑 인터페이스인 매개변수가 하나만 있습니다. MapperProxyFactory의 다른 속성은 수정이 허용되지 않으므로 생성되는 MapperProxy 개체는 고유합니다. 따라서 MapperProxyFactory 객체가 결정되면 MapperProxy 객체도 결정됩니다. 따라서 MapperRegistry의 KnownMappers 속성은 매핑 인터페이스를 MapperProxy 개체와 간접적으로 연결합니다.

public class MapperProxyFactory<T> {
    
    
 /**
   * MapperProxyFactory构造方法
   * @param mapperInterface 映射接口
   */
  public MapperProxyFactory(Class<T> mapperInterface) {
    
    
    this.mapperInterface = mapperInterface;
  }

MapperRegistry는 매핑 인터페이스와 MapperProxy 간의 해당 관계를 저장하므로 해당 getMapper 메서드는 매핑 인터페이스에 대한 해당 프록시 개체를 직접 찾을 수 있습니다. MapperRegistry를 통해 매핑 인터페이스와 매핑 파일 간의 해당 관계가 설정됩니다.

  • 두 번째 단계에서는 이때 범위가 매핑 인터페이스 또는 MapperProxy 개체로 축소되었습니다. 인터페이스 메소드와 MapperMethod 객체 간의 해당 관계는 MapperProxy의 methodCache 속성에 의해 유지됩니다.
    이러한 방식으로 매핑 인터페이스의 모든 추상 메서드는 MapperProxy 개체와 연결된 MapperMethod 개체에 해당합니다.
  // 该Map的键为方法,值为MapperMethod对象。通过该属性,完成了MapperProxy内(即映射接口内)方法和MapperMethod的绑定
  private final Map<Method, MapperMethod> methodCache;

여기에 이미지 설명을 삽입하세요.
MapperProxy 클래스는 매핑 인터페이스의 프록시 클래스입니다. 프록시 관계가 설정된 후 매핑 인터페이스의 메서드가 호출되는 동안 해당 MapperProxy가 이를 가로채고 MapperProxy는 적절한 MapperMethod 개체를 생성하거나 선택하고 해당 실행 메서드를 트리거합니다. 결과적으로 매핑 인터페이스의 추상 메서드에 대한 호출은 특정 데이터베이스 작업으로 변환됩니다.

2.1.4 데이터베이스 운영 접근 요약

  1. 초기화 단계

MyBatis는 초기화 단계에서 각 매핑 파일을 구문 분석한 다음 각 데이터베이스 작업 노드의 정보를 Configuration 객체의 mappedStatements 속성에 기록합니다. 그 구조는 StrictMap(키 값의 덮어쓰기를 허용하지 않는 HashMap)이며, 이 StrictMap의 키는 SQL 문의 "네임스페이스 값. 명령문 id 값"(명령문 id 값이 모호하지 않은 경우 명령문 ID)입니다. 값은 별도로 사용됩니다. 데이터 사본에 입력), 값은 데이터베이스 운영 노드의 세부 정보입니다.

MyBatis는 또한 초기화 단계에서 모든 매핑 인터페이스를 스캔하고 매핑 인터페이스를 기반으로 이와 관련된 MapperProxyFactory를 생성합니다. 둘 사이의 관계는 MapperRegistry에 의해 유지됩니다. MapperRegistry의 getMapper 메소드가 호출되면(SqlSession의 getMapper 메소드는 결국 여기에서 호출됩니다) MapperProxyFactory는 매핑 인터페이스의 프록시로 MapperProxy 객체를 생성합니다.

  1. 데이터 읽기 및 쓰기 단계
    매핑 인터페이스의 메소드가 호출되면 프록시 객체 MapperProxy에 의해 하이재킹되며, 이는 차례로 MapperProxy 객체의 호출 메소드를 트리거합니다. MapperProxy 객체의 호출 메소드는 매핑 인터페이스 메소드에 해당하는 MapperMethod 객체를 생성하거나 검색합니다. MapperMethod 객체를 생성하는 과정에서 MapperMethod의 SqlCommand 하위 클래스 생성자는 Configuration 객체의 mappedStatements 속성으로 이동합니다. 현재 매핑 인터페이스 이름과 메소드에 초기 이름 인덱스에 저장되어 있던 SQL 문 정보가 저장된다. 그런 다음 MapperMethod 개체의 실행 메서드가 트리거되고 실행 메서드의 SQL 문 유형에 따라 서로 다른 데이터베이스 작업이 수행됩니다. 이러한 방식으로 매핑 인터페이스의 메서드 호출은 최종적으로 해당 데이터베이스 작업으로 변환됩니다.

3.빌더 패키지

빌더 패키지는 종류별로 나누어진 패키지로, 패키지 내에는 다양한 빌더 클래스가 존재한다.
빌더 패키지는 유형별로 구분된 패키지이지만 비교적 완전한 다음 두 가지 기능도 이 패키지에 완성되어 있습니다.

  1. XML 구성 파일 및 매핑 파일을 구문 분석합니다. 함수의 이 부분은 xml 하위 패키지에 있습니다.
  2. 주석 형태로 매퍼 선언을 구문 분석합니다. 함수의 이 부분은 주석 하위 패키지에 있습니다.

빌더 패턴의 장점:

  • 빌더를 사용할 때 매우 유연하며 빌드된 개체의 속성을 한 번 또는 여러 번 설정할 수 있습니다.
  • 호출자는 빌더의 기본 프로세스만 호출하면 되며 빌드된 개체의 세부 사항에 대해서는 걱정할 필요가 없습니다.
  • 빌더의 동작을 쉽게 수정하여 다양한 개체를 만들 수 있습니다.

빌더 클래스에는 일반적으로 두 가지 유형의 메소드가 포함됩니다.

  • 한 가지 유형은 속성 설정 방법입니다. 일반적으로 빌더의 속성을 설정하기 위해 다양한 유형의 매개변수를 허용할 수 있는 이 유형의 여러 메소드가 있습니다.
  • 한 가지 유형은 대상 개체 생성 방법입니다. 이러한 유형의 메소드에는 일반적으로 현재 빌더의 특성을 기반으로 대상 객체를 생성하는 단 하나의 메소드만 있습니다.
    빌더 패턴의 장점은 복잡한 객체를 생성할 때 더욱 분명해집니다. 따라서 빌더 패턴은 일부 대규모 시스템에서 매우 일반적입니다.

3.1 빌더 기본 클래스 및 도구 클래스

BaseBuilder는 모든 빌더 클래스의 기본 클래스로,
여기에 이미지 설명을 삽입하세요.
BaseBuilder 클래스는 추상 클래스로 선언되어 있지만 추상 메서드가 포함되어 있지 않으므로 해당 하위 클래스에서는 해당 메서드를 구현할 필요가 없습니다. BaseBuilder 클래스는 이를 상속하는 빌더 클래스에 대해 많은 실용적인 유틸리티 메서드를 제공하는 유틸리티 클래스와 비슷합니다. 물론 BaseBuilder에서 제공하는 도구 메소드가 필요하지 않아 BaseBuilder를 상속하지 않는 빌더 클래스도 실제로 많으며 이러한 클래스에는 MapperAnnotationBuilder, SelectBuilder 등이 있습니다.

BaseBuilder 클래스에서 제공하는 도구 메서드는 대략 다음과 같은 범주로 구분됩니다.

  • *ValueOf: 입력 매개변수를 지정된 유형으로 변환하고 기본값 설정을 지원하는 유형 변환 함수입니다.
  • 해결*: 열거형 함수에 대한 문자열, 문자열을 기반으로 지정된 열거형을 찾아서 반환합니다.
  • createInstance: 유형 별칭을 기반으로 유형 인스턴스를 만듭니다.
  • solveTypeHandler: 유형 핸들러 별칭을 기반으로 유형 핸들러 인스턴스를 반환합니다.

BaseBuilder 클래스의 하위 클래스 중에서 MapperBuilderAssistant 클래스는 빌더 클래스 자체가 아니라 빌더 보조 클래스라는 점에서 가장 특별합니다. BaseBuilder 클래스에서 상속받는 이유는 단순히 BaseBuilder 클래스의 메서드를 사용하기 위해서입니다.

MyBatis 매핑 파일에는 네임스페이스, 캐시 공유, 결과 매핑 등을 포함한 많은 설정이 있습니다. 결국 이러한 설정은 구문 분석되어 다양한 클래스를 생성하며 MapperBuilderAssistant 클래스는 이러한 구문 분석된 클래스에 대한 보조 클래스입니다. MapperBuilderAssistant 클래스는 Mapper 네임스페이스 설정, 캐시 생성, 판별자 생성 등과 같은 많은 보조 메서드를 제공합니다.

3.2 SqlSourceBuilder 클래스와 StaticSqlSource 클래스

SqlSourceBuilder는 빌더 클래스이지만 이름이 다소 모호하며 모든 SqlSource 개체를 생성하는 데 사용할 수는 없지만(SqlSource는 4가지 구현을 포함하는 인터페이스) Parsing 메서드를 통해서만 StaticSqlSource 개체를 생성할 수 있습니다.

정확하게 말하면 SqlSourceBuilder 클래스는 DynamicSqlSource와 RawSqlSource의 "#{}" 기호를 대체하여 StaticSqlSource로 변환할 수 있는데, 이 변환 과정은 구문 분석 메서드에서 발생합니다. 따라서 SqlSourceBuilder 클래스를 파서 또는 변환기라고 부르는 것이 더 적절합니다. 실제로 SqlSourceBuilder 개체를 참조하는 많은 곳에서는 개체의 변수 이름을 "sqlSourceParser"로 지정합니다(이 변수는 DynamicSqlSource 및 RawSqlSource 클래스에서 찾을 수 있음).

 /**
   * 将DynamicSqlSource和RawSqlSource中的“#{}”符号替换掉,从而将他们转化为StaticSqlSource
   * @param originalSql sqlNode.apply()拼接之后的sql语句。已经不包含<if> <where>等节点,也不含有${}符号
   * @param parameterType 实参类型
   * @param additionalParameters 附加参数
   * @return 解析结束的StaticSqlSource
   */
  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    
    
    // 用来完成#{}处理的处理器
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    // 通用的占位符解析器,用来进行占位符替换
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    // 将#{}替换为?的SQL语句
    String sql = parser.parse(originalSql);
    // 生成新的StaticSqlSource对象
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

StaticSqlSource는 SqlSource의 네 가지 하위 클래스 중 하나입니다. "${}" 및 "#{}"라는 두 기호는 포함된 SQL 문에 더 이상 존재하지 않고 "?"만 존재합니다.

public class StaticSqlSource implements SqlSource {
    
    

  // 经过解析后,不存在${}和#{}这两种符号,只剩下?符号的SQL语句
  private final String sql;
  // SQL语句对应的参数列表
  private final List<ParameterMapping> parameterMappings;
  // 配置信息
  private final Configuration configuration;
  /**
   * 组建一个BoundSql对象
   * @param parameterObject 参数对象
   * @return 组件的BoundSql对象
   */
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    
    
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }
}

StaticSqlSource에는 BoundSql 개체를 제공하는 매우 중요한 기능이 있습니다. StaticSqlSource의 getBoundSql 메소드는 이 기능을 완료하는 역할을 합니다.

3.3 CacheRefResolver 클래스와 ResultMapResolver 클래스

CacheRefResolver 클래스와 ResultMapResolver 클래스는 클래스 이름뿐만 아니라 구조와 기능에서도 다소 유사합니다. 이들은 모두 특정 클래스의 파서 클래스이며, 속성에는 파싱된 클래스의 관련 속성과 파서가 포함되어 있습니다. 클래스의 구문 분석기는 구문 분석된 클래스 속성의 구문 분석을 완료할 수 있습니다. 구문 분석 기능을 갖춘 이러한 통합 클래스는 MyBatis에서 표준화된 이름을 갖습니다. 구문 분석된 객체의 이름이 A인 경우 통합된 자체 구문 분석 클래스는 AResolver라고 합니다.

이후 분석에서 이와 같은 이름의 클래스를 만나면 그 구성과 기능을 직접적으로 분석할 수 있다. 이 명명 방법과 기능은 상대적으로 보편적이지만 절대적인 것은 아닙니다. 예를 들어, 주석 하위 패키지의 MethodResolver는 구문 분석된 객체의 속성과 구문 분석기를 포함하여 이 패턴을 따르지만 ParamNameResolver는 구문 분석 기능이 자체 메서드를 통해 구현되고 의존할 필요가 없기 때문에 이 패턴을 따르지 않습니다. 다른 파서에서.

<mapper namespace="com.github.yeecode.mybatisdemo.UserDao">
    <cache-ref namespace="com.github.yeecode.mybatisdemo"/>

MyBatis는 여러 네임스페이스 간의 공유 캐시를 지원합니다. "com.github.yeecode.mybatisdemo.dao.UserDao" 네임스페이스에서 <cache-ref> 태그를 통해 또 다른 네임스페이스 "com.github.yeecode.mybatisdemo.dao.TaskDao"를 선언합니다. 전자는 후자를 사용합니다. 은닉처.

3.3.1 CacheRefResolver 클래스

CacheRefResolver는 캐시를 공유하는 여러 네임스페이스 문제를 처리하는 데 사용됩니다. 그것은 그 자체로 두 가지 속성을 가지고 있습니다. 이 두 속성 중 Assistant는 파서이고, 캐시RefNamespace는 파싱된 개체입니다.

/**
 * @author Clinton Begin
 *
 * 缓存引用解析器
 *
 * 包含了被解析的对象cacheRefNamespace 和对应的解析器MapperBuilderAssistant 因此具有自解析功能。
 */
public class CacheRefResolver {
    
    
  // Mapper建造者辅助类
  private final MapperBuilderAssistant assistant;
  // 被应用的namespace,即使用cacheRefNamespace的缓存空间
  private final String cacheRefNamespace;

  public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
    
    
    this.assistant = assistant;
    this.cacheRefNamespace = cacheRefNamespace;
  }

  public Cache resolveCacheRef() {
    
    
    return assistant.useCacheRef(cacheRefNamespace);
  }
}

MapperBuilderAssistant의 useCacheRef 메소드를 사용하면 CacheRefResolver 클래스가 캐시 공유 문제를 해결할 수 있습니다.

  /**
   * 使用其他namespace的缓存
   * @param namespace 其他的namespace
   * @return  其他namespace的缓存
   */
  public Cache useCacheRef(String namespace) {
    
    
    if (namespace == null) {
    
    
      throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
    
    
      unresolvedCacheRef = true;
      // 获取其他namespace的缓存
      Cache cache = configuration.getCache(namespace);
      if (cache == null) {
    
    
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
      }
      // 修改当前缓存为其他namespace的缓存,从而实现缓存共享
      currentCache = cache;
      unresolvedCacheRef = false;
      return cache;
    } catch (IllegalArgumentException e) {
    
    
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
    }
  }

3.3.2 ResultMapResolver 클래스

MyBatis의 resultMap 태그는 상속을 지원합니다. 코드 14-13과 같이 "girlUserMap"은 "extends="userMap""을 설정하여 "userMap"에 설정된 속성 매핑을 상속합니다.

    <resultMap id="userMap" type="User" autoMapping="false">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <discriminator javaType="int" column="sex">
            <case value="0" resultMap="boyUserMap"/>
            <case value="1" resultMap="girlUserMap"/>
        </discriminator>
    </resultMap>

    <resultMap id="girlUserMap" type="Girl" extends="userMap">
        <result property="email" column="email"/>
    </resultMap>

resultMap 상속 관계의 해결은 ResultMapResolver 클래스에 의해 완료됩니다. ResultMapResolver 클래스의 속성 보조 속성은 파서이고, 다른 속성은 구문 분석된 속성입니다.

public class ResultMapResolver {
    
    
  // Mapper建造者辅助类
  private final MapperBuilderAssistant assistant;
  // ResultMap的id
  private final String id;
  // ResultMap的type属性,即目标对象类型
  private final Class<?> type;
  // ResultMap的extends属性,即继承属性
  private final String extend;
  // ResultMap中的Discriminator节点,即鉴别器
  private final Discriminator discriminator;
  // ResultMap中的属性映射列表
  private final List<ResultMapping> resultMappings;
  // ResultMap的autoMapping属性,即是否开启自动映射
  private final Boolean autoMapping;

MapperBuilderAssistant의 addResultMap 메소드를 사용하여 ResultMapResolver는 ResultMap의 상속 관계 분석을 완료하고 상속 관계가 해결된 후 최종적으로 ResultMap 객체를 제공합니다.


  /**
   * 创建结果映射对象
   * 入参参照ResultMap属性
   * @return ResultMap对象
   */
  public ResultMap addResultMap(
      String id,
      Class<?> type,
      String extend,
      Discriminator discriminator,
      List<ResultMapping> resultMappings,
      Boolean autoMapping) {
    
    
    id = applyCurrentNamespace(id, false);
    extend = applyCurrentNamespace(extend, true);

    // 解析ResultMap的继承关系
    if (extend != null) {
    
     // 如果存在ResultMap的继承
      if (!configuration.hasResultMap(extend)) {
    
    
        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
      }
      // 获取父级的ResultMap
      ResultMap resultMap = configuration.getResultMap(extend);
      // 获取父级的属性映射
      List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
      // 删除当前ResultMap中已有的父级属性映射,为当前属性映射覆盖父级属性属性创造条件
      extendedResultMappings.removeAll(resultMappings);
      // 如果当前ResultMap设置有构建器,则移除父级构建器
      boolean declaresConstructor = false;
      for (ResultMapping resultMapping : resultMappings) {
    
    
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
    
    
          declaresConstructor = true;
          break;
        }
      }
      if (declaresConstructor) {
    
    
        extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
      }
      // 最终从父级继承而来的所有属性映射
      resultMappings.addAll(extendedResultMappings);
    }
    // 创建当前的ResultMap
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    // 将当期的ResultMap加入到Configuration
    configuration.addResultMap(resultMap);
    return resultMap;
  }

3.4 ParameterExpression 클래스

ParameterExpression은 속성을 설명하는 문자열을 키-값 쌍으로 구문 분석하는 속성 파서입니다. ParameterExpression의 생성자는 특성 구문 분석을 위한 일반적인 진입점이며 전체 클래스에서 유일한 공용 메서드이기도 합니다. ParameterExpression 클래스는 HashMap을 상속하며 최종 구문 분석 결과를 키-값 쌍의 형태로 내부적으로 저장할 수 있습니다.

/**
 * 一个属性解析器
 * 能够将属性拆解开来
 */
public class ParameterExpression extends HashMap<String, String> {
    
    

  private static final long serialVersionUID = -2417552199605158680L;

  public ParameterExpression(String expression) {
    
    
    parse(expression);
  }

  // content = id, javaType= int, jdbcType=NUMERIC, typeHandler=DemoTypeHandler ;
  private void parse(String expression) {
    
    
    // 跳过空格
    int p = skipWS(expression, 0);
    // 跳过左括号
    if (expression.charAt(p) == '(') {
    
    
      expression(expression, p + 1);
    } else {
    
    
      // 处理参数
      property(expression, p);
    }
  }

3.5 XML 파일 구문 분석

MyBatis의 구성 파일과 매핑 파일은 XML 파일이며 궁극적으로 이러한 XML 파일은 해당 클래스로 구문 분석되어야 합니다. 빌더 패키지의 xml 하위 패키지는 XML 파일 구문 분석을 완료하는 데 사용됩니다. MyBatis의 구성 파일과 매핑 파일에는 많은 노드가 포함되어 있습니다. 이러한 노드의 구문 분석은 xml 하위 패키지의 5개 구문 분석기 클래스에 의해 계층별로 완료됩니다.
여기에 이미지 설명을 삽입하세요.

3.5.1 XML 파일 선언 구문 분석

XML 파일은 외부 DTD 파일을 참조하여 XML 파일을 확인할 수 있습니다. 위 그림의 DOCTYPE 문에서는 현재 XML 파일이 참조하는 DTD 파일의 주소가 "http://mybatis.org/dtd/mybatis-3-config.dtd"임을 나타냅니다. MyBatis는 네트워크가 아닌 환경에서 실행될 수 있으며 인터넷을 통해 DTD 파일을 다운로드할 수 없습니다. XMLMapperEntityResolver는 이 문제를 해결하는 데 사용됩니다.
"org.xml.sax.EntityResolver" 인터페이스에는 verifyEntity 메소드가 있습니다. 인터넷에서 DTD 문서를 다운로드하는 대신 이 메소드를 구현하여 DTD 문서 스트림이 제공되는 방식을 사용자 정의할 수 있습니다.

  /**
   * 在一个XML文件的头部是这样的:
   * <!DOCTYPE configuration
   *         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
   *         "http://mybatis.org/dtd/mybatis-3-config.dtd">
   *  那么上述例子中,
   * @param publicId 为-//mybatis.org//DTD Config 3.0//EN
   * @param systemId 为http://mybatis.org/dtd/mybatis-3-config.dtd
   * @return 对应DTD文档的输入流
   * @throws SAXException
   */
  @Override
  public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
    
    
    try {
    
    
      if (systemId != null) {
    
    
        // 将systemId转为全小写
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
    
    
          // 说明这个是配置文档
          // 直接把本地配置文档的dtd文件返回
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
        } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
    
    
          // 说明这个是映射文档
          // 直接把本地映射文档的dtd文件返回
          return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
        }
      }
      return null;
    } catch (Exception e) {
    
    
      throw new SAXException(e.toString());
    }
  }

3.5.2 구성 파일 분석

XMLConfigBuilder 클래스는 구성 파일을 구문 분석하는 역할을 하며 이 클래스는 구문 분석 결과를 사용하여 구성 개체를 만듭니다. XMLConfigBuilder 클래스의 진입 메소드는 구성 파일의 계층별 구문 분석을 공식적으로 시작하기 위해 구문 분석 메소드를 호출하는 구문 분석 메소드입니다.

  /**
   * 从根节点configuration开始解析下层节点
   * @param root 根节点configuration节点
   */
  private void parseConfiguration(XNode root) {
    
    
    try {
    
    
      // 解析信息放入Configuration
      // 首先解析properties,以保证在解析其他节点时便可以生效
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
    
    
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

parseConfiguration 메소드는 하위 노드를 구문 분석하기 위해 다양한 하위 메소드를 호출하며 이러한 메소드는 유사합니다. "/configuration/mappers" 노드를 파싱하는 mapperElement 메소드를 예로 들어 소개하겠습니다.

/**
   * 解析mappers节点,例如:
   * <mappers>
   *    <mapper resource="com/github/yeecode/mybatisDemo/UserDao.xml"/>
   *    <package name="com.github.yeecode.mybatisDemo" />
   * </mappers>
   * @param parent mappers节点
   * @throws Exception
   */
  private void mapperElement(XNode parent) throws Exception {
    
    

    if (parent != null) {
    
    
      for (XNode child : parent.getChildren()) {
    
    
        // 处理mappers的子节点,即mapper节点或者package节点
        if ("package".equals(child.getName())) {
    
     // package节点
          // 取出包的路径
          String mapperPackage = child.getStringAttribute("name");
          // 全部加入Mappers中
          configuration.addMappers(mapperPackage);
        } else {
    
    
          // resource、url、class这三个属性只有一个生效
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
    
    
            ErrorContext.instance().resource(resource);
            // 获取文件的输入流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 使用XMLMapperBuilder解析Mapper文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
    
    
            ErrorContext.instance().resource(url);
            // 从网络获得输入流
            InputStream inputStream = Resources.getUrlAsStream(url);
            // 使用XMLMapperBuilder解析Mapper文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
    
    
            // 配置的不是Mapper文件,而是Mapper接口
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
    
    
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

3.5.3 데이터베이스 연산문 분석

XMLMapperBuilder 클래스는 매핑 파일을 구문 분석하는 역할을 하며 이 클래스의 구조는 XMLConfigBuilder 클래스와 매우 유사합니다. 구문 분석 메소드는 구문 분석을 위한 진입 메소드이며, 그 후 구성요소 메소드를 호출하여 계층별로 구문 분석을 완료합니다.

  /**
   * 解析Mapper文件
   */
  public void parse() {
    
    
    // 该节点是否被解析过
    if (!configuration.isResourceLoaded(resource)) {
    
    
      // 处理mapper节点
      configurationElement(parser.evalNode("/mapper"));
      // 加入到已经解析的列表,防止重复解析
      configuration.addLoadedResource(resource);
      // 将mapper注册给Configuration
      bindMapperForNamespace();
    }

    // 下面分别用来处理失败的<resultMap>、<cache-ref>、SQL语句
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

  /**
   * 解析Mapper文件的下层节点
   * @param context Mapper文件的根节点
   */
  private void configurationElement(XNode context) {
    
    
    try {
    
    
      // 读取当前Mapper文件的命名空间
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
    
    
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      // mapper文件中其他配置节点的解析
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      // 处理各个数据库操作语句
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
    
    
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

XMLConfigBuilder 클래스의 구문 분석 메소드와 달리 XMLMapperBuilder의 구문 분석 메소드 끝에는 3개의 구문 분석Pending* 메소드가 있습니다. 구문 분석 중 일시적인 오류를 처리하는 데 사용됩니다. 구성Element(parser.evalNode("/mapper")) 문에 의해 트리거된 후 시스템은 매핑 파일의 각 노드를 순서대로 구문 분석합니다. 파싱할 때 파일을 위에서 아래로 읽어서 파싱하는데, 노드를 파싱할 수도 있지만 참조하는 노드가 아직 정의되지 않은 상태이다.

    <resultMap id="userMap" type="User" autoMapping="false">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <discriminator javaType="int" column="sex">
            <case value="0" resultMap="boyUserMap"/>
            <case value="1" resultMap="girlUserMap"/>
        </discriminator>
    </resultMap>

	<resultMap id="girlUserMap" type="Girl" extends="userMap">
        <result property="email" column="email"/>
    </resultMap>

"id="girlUserMap""의 resultMap을 파싱할 때, "extends="userMap""을 통해 참조하는 "id="userMap""의 resultMap은 아직 읽혀지지 않았습니다. 이때 일시적인 오류가 발생합니다.
일시적인 오류가 있는 노드를 저장하는 데 사용되는 구성 코드의 여러 속성이 있습니다.

  // 暂存未处理完成的一些节点
  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

위에서 언급한 종속성을 확인할 수 없는 상황은 일시적인 현상이므로 첫 번째 구문 분석이 완료된 후에 해당 오류 노드를 다시 처리하면 됩니다. 순서가 잘못된 종속성을 해결하는 일반적인 방법은 첫 번째 구문 분석을 시도하고 구문 분석하는 동안 모든 노드를 읽는 것입니다. 그런 다음 두 번째 구문 분석이 수행되고 첫 번째 구문 분석을 찾지 못한 노드에 의존합니다. 첫 번째 패스에서 모든 노드를 읽었으므로 두 번째 패스의 종속성을 항상 찾을 수 있습니다.

보다 직접적이고 간단한 또 ​​다른 방법이 있습니다. 즉, 첫 번째 구문 분석에서 모든 노드를 읽기만 하고 종속성을 처리하지 않고 두 번째 구문 분석에서 종속성만 처리하는 것입니다. 이것이 Spring이 초기화 중에 Bean 간의 종속성을 처리하는 방법입니다.

3.5.4 진술 분석

매핑 파일의 구문 분석에 있어서 중요한 작업은 데이터베이스 작업 노드, 즉 선택, 삽입, 업데이트, 삭제의 네 가지 유형의 노드를 구문 분석하는 것입니다. 데이터베이스 작업 노드의 구문 분석은 XMLStatementBuilder에 의해 완료됩니다. XMLStatementBuilder 클래스의 parseStatementNode 메서드는 기본 구문 분석 프로세스를 완료합니다.

  /**
   * 解析select、insert、update、delete这四类节点
   */
  public void parseStatementNode() {
    
    
    // 读取当前节点的id与databaseId
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    // 验证id与databaseId是否匹配。MyBatis允许多数据库配置,因此有些语句只对特定数据库生效
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    
    
      return;
    }

    // 读取节点名
    String nodeName = context.getNode().getNodeName();
    // 读取和判断语句类型
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 处理语句中的Include节点
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());
    // 参数类型
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    // 语句类型
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // 处理SelectKey节点,在这里会将KeyGenerator加入到Configuration.keyGenerators中
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // 此时,<selectKey> 和 <include> 节点均已被解析完毕并被删除,开始进行SQL解析
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    // 判断是否已经有解析好的KeyGenerator
    if (configuration.hasKeyGenerator(keyStatementId)) {
    
    
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
    
    
      // 全局或者本语句只要启用自动key生成,则使用key生成
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 读取各个配置属性
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
    
    
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");
    // 在MapperBuilderAssistant的帮助下创建MappedStatement对象,并写入到Configuration中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

3.5.5 애플리케이션 구문 분석에는 다음이 포함됩니다.

MyBatis는 데이터베이스 작업 명령문 작성 시 명령문 조각 인용을 지원합니다. MyBatis에서 데이터베이스 작업문 작성 효율성을 향상시킵니다.

    <sql id="bySchool">
        AND `schoolName` = #{
    
    schoolName}
    </sql>

    <select id="selectUserByNameAndSchoolName" parameterMap="userParam01" resultType="User">
        SELECT * FROM `user` WHERE `name` = #{
    
    name}
        <include refid="bySchool"/>
    </select>

    <select id="selectUsersByNameOrSchoolName" parameterMap="userParam01" resultType="User">
        SELECT * FROM `user`
        <where>
            <if test="name != null">
                `name` = #{
    
    name}
            </if>
            <if test="schoolName != null">
                AND `schoolName` = #{
    
    schoolName}
            </if>
        </where>
    </select>

코드의 두 가지 메소드 selectUserByNameAndSchoolName은 동일합니다. 포함 노드의 구문 분석은 SQL 문의 포함 노드를 인용된 SQL 조각으로 바꾸는 XMLIncludeTransformer에 의해 처리됩니다. XMLIncludeTransformer 클래스의 applyIncludes(Node) 메소드는 인클루드 노드를 구문 분석하기 위한 진입 메소드이고, applyIncludes(Node, Properties, boolean) 메소드는 핵심 메소드입니다.

/**
   * 解析数据库操作节点中的include节点
   * @param source 数据库操作节点或其子节点
   * @param variablesContext 全局属性信息
   * @param included 是否嵌套
   */
  private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
    
    
    if (source.getNodeName().equals("include")) {
    
     // 当前节点是include节点
      // 找出被应用的节点
      Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
      Properties toIncludeContext = getVariablesContext(source, variablesContext);
      // 递归处理被引用节点中的include节点
      applyIncludes(toInclude, toIncludeContext, true);
      if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
    
    
        toInclude = source.getOwnerDocument().importNode(toInclude, true);
      }
      // 完成include节点的替换
      source.getParentNode().replaceChild(toInclude, source);
      while (toInclude.hasChildNodes()) {
    
    
        toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
      }
      toInclude.getParentNode().removeChild(toInclude);
    } else if (source.getNodeType() == Node.ELEMENT_NODE) {
    
     // 元素节点
      if (included && !variablesContext.isEmpty()) {
    
    
        // 用属性值替代变量
        NamedNodeMap attributes = source.getAttributes();
        for (int i = 0; i < attributes.getLength(); i++) {
    
    
          Node attr = attributes.item(i);
          attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
        }
      }
      // 循环到下层节点递归处理下层的include节点
      NodeList children = source.getChildNodes();
      for (int i = 0; i < children.getLength(); i++) {
    
    
        applyIncludes(children.item(i), variablesContext, included);
      }
    } else if (included && source.getNodeType() == Node.TEXT_NODE
        && !variablesContext.isEmpty()) {
    
     // 文本节点
      // 用属性值替代变量
      source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
    }
  }

여기에 이미지 설명을 삽입하세요.

3.6 주석 매핑 분석

일반적으로 우리는 MyBatis 매핑 구성을 완료하기 위해 XML 매핑 파일을 사용합니다. 동시에 MyBatis는 매핑을 구성하기 위한 주석 사용도 지원합니다. 빌더 패키지의 주석 하위 패키지를 사용하여 이러한 형태의 매핑 분석을 완료할 수 있습니다. 매핑을 구성하기 위해 주석을 사용하는 방법은 덜 자주 사용될 수 있습니다.이 섹션에서는 먼저 이 구성 방법을 소개한 다음 MyBatis가 주석 매핑을 구문 분석하는 방법을 이해하기 위해 주석 하위 패키지의 소스 코드를 읽을 것입니다.

3.6.1 주석 사용

매핑 인터페이스의 추상 메서드에 주석을 추가하여 추상 메서드와 연결된 데이터베이스 작업 명령문을 선언할 수 있습니다.
@Select 주석 외에도 @Insert, @Update 및 @Delete 주석도 비슷한 기능을 수행할 수 있습니다.

    @Select("SELECT * FROM `user` WHERE `id` = #{id}")
    User queryUserById(Integer id);

    @Select("<script>" +
            "        SELECT *\n" +
            "        FROM `user`\n" +
            "        WHERE id IN\n" +
            "        <foreach item=\"id\" collection=\"array\" open=\"(\" separator=\",\" close=\")\">\n" +
            "            #{id}\n" +
            "        </foreach>\n" +
            "    </script>")
    List<User> queryUsersByIds(int[] ids);

MyBatis는 또한 보다 유연한 주석 방법을 지원합니다.

@SelectProvider(type = UserProvider.class, method = "queryUsersBySchoolName")
List<User> queryUsersBySchoolName(String schoolName);

이런 방식으로 @SelectProvider 주석을 추상 메서드에 추가할 수 있습니다. 주석의 유형 필드는 클래스를 가리키고 메서드는 클래스의 메서드를 가리킵니다. 마지막으로, type 클래스의 메소드 메소드에 의해 반환된 문자열은 queryUserBySchoolName 메소드에 바인딩된 SQL 문으로 사용됩니다.

마찬가지로 @SelectProvider 주석 외에도 @InsertProvider, @UpdateProvider 및 @DeleteProvider라는 세 가지 주석이 있습니다.
네 가지 주석 메서드 @Select, @Insert, @Update 및 @Delete를 직접 주석 매핑이라고 하며, 네 가지
주석 메서드 @SelectProvider, @InsertProvider, @UpdateProvider 및 @DeleteProvider를 간접 주석 매핑이라고 합니다.

3.6.2 주석 매핑 분석

주석 매핑 구문 분석은 MapperAnnotationBuilder 클래스의 구문 분석 메서드에서 시작됩니다. 이 메서드가 트리거되기 전에 MapperAnnotationBuilder 클래스는 정적 코드 블록에서 일부 초기화 작업을 완료했습니다. 즉, 직접 주석 매핑의 4개 주석은 SQL_ANNOTATION_TYPES 상수에 입력되고, 간접 주석 매핑의 4개 주석은 SQL_PROVIDER_ANNOTATION_TYPES 상수에 입력됩니다. .

  static {
    
    
    SQL_ANNOTATION_TYPES.add(Select.class);
    SQL_ANNOTATION_TYPES.add(Insert.class);
    SQL_ANNOTATION_TYPES.add(Update.class);
    SQL_ANNOTATION_TYPES.add(Delete.class);

    SQL_PROVIDER_ANNOTATION_TYPES.add(SelectProvider.class);
    SQL_PROVIDER_ANNOTATION_TYPES.add(InsertProvider.class);
    SQL_PROVIDER_ANNOTATION_TYPES.add(UpdateProvider.class);
    SQL_PROVIDER_ANNOTATION_TYPES.add(DeleteProvider.class);
  }

매퍼 태그의 구성이 구성 파일에 존재하는 경우 MapperAnnotationBuilder 클래스의 구문 분석 메서드가 트리거되어 매핑 인터페이스 파일 구문 분석을 시작합니다.

<mappers>
    <mapper resource="com/github/yeecode/mybatisdemo/UserDao.xml"/>
</mappers>
 /**
   * 解析包含注解的接口文档
   */
  public void parse() {
    
    
    String resource = type.toString();
    // 防止重复分析
    if (!configuration.isResourceLoaded(resource)) {
    
    
      // 寻找类名对应的resource路径下是否有xml配置,如果有则解析掉。这样就支持注解和xml混合使用
      loadXmlResource();
      // 记录资源路径
      configuration.addLoadedResource(resource);
      // 设置命名空间
      assistant.setCurrentNamespace(type.getName());
      // 处理缓存
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
    
    
        try {
    
    
          // 排除桥接方法
          // JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法,这个就是桥接方法。
          // 就是说一个子类在继承(或实现)一个父类(或接口)的泛型方法时,在子类中明确指定了泛型类型,那么在编译时编译器会自动生成桥接方法
          if (!method.isBridge()) {
    
    
            // 解析该方法
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
    
    
          // 解析异常的方法暂存起来
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    // 处理解析异常的方法
    parsePendingMethods();
  }

코드의 "!method.isBridge()" 문은 브리지 메서드를 제외하는 것입니다. 브릿지 메소드는 제네릭의 삭제 유형과 일치하도록 컴파일러에 의해 자동으로 도입되며, 사용자가 작성한 메소드가 아니므로 제외해야 합니다.
parsePendingMethods 메서드, 인터페이스 메서드를 구문 분석할 때 구문 분석되지 않은 ResultMap 정보, 구문 분석되지 않은 네임스페이스 등과 같이 읽지 않은 다른 정보가 발생할 수 있습니다. 그런 다음 메서드는 incompleteMethods에 배치됩니다. Configuration 클래스의 속성은 마지막에 다시 처리됩니다. 다시 처리할 때 MethodResolver.parseStatement 메서드를 사용하여 다시 구문 분석에 실패한 인터페이스 메서드를 구문 분석합니다.

 /**
   * 解析该方法。主要是解析该方法上的注解信息
   * @param method 要解析的方法
   */
  void parseStatement(Method method) {
    
    
    // 通过子方法获取参数类型
    Class<?> parameterTypeClass = getParameterType(method);
    // 获取方法的脚本语言驱动
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 通过注解获取SqlSource
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
    
    
      // 获取方法上可能存在的配置信息,配置信息由@Options注解指定
      Options options = method.getAnnotation(Options.class);
      final String mappedStatementId = type.getName() + "." + method.getName();
      // 用默认值初始化各项设置
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = configuration.getDefaultResultSetType();
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;

      // 主键自动生成的处理
      KeyGenerator keyGenerator;
      String keyProperty = null;
      String keyColumn = null;
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
    
    
        // first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {
    
    
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else if (options == null) {
    
    
          // 这里不能单独配置,因此查看全局配置
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        } else {
    
    
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      } else {
    
    
        keyGenerator = NoKeyGenerator.INSTANCE;
      }

      if (options != null) {
    
    
        // 根据@Options中的配置信息重新设置配置
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
    
    
          flushCache = true;
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
    
    
          flushCache = false;
        }
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        if (options.resultSetType() != ResultSetType.DEFAULT) {
    
    
          resultSetType = options.resultSetType();
        }
      }

      // 返回结果ResultMap处理
      String resultMapId = null;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {
    
    
        resultMapId = String.join(",", resultMapAnnotation.value());
      } else if (isSelect) {
    
    
        resultMapId = parseResultMap(method);
      }

      // 将获取的映射信息存入Configuration
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }

ParseStatement 메소드는 매개변수 및 구성 정보와 같은 추가 정보를 처리하며, 가장 중요한 것은 getSqlSourceFromAnnotations 메소드를 호출하여 SqlSource 객체를 얻는 것입니다.

 /**
   * 通过注解获取SqlSource对象
   * @param method 含有注解的方法
   * @param parameterType 参数类型
   * @param languageDriver 语言驱动
   * @return SqlSource对象
   */
  private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
    
    
    try {
    
    
      // 遍历寻找是否有Select、Insert、Update、Delete 四个注解之一
      Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
      // 遍历寻找是否有SelectProvider、insertProvider、UpdateProvider、DeleteProvider四个注解之一
      Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
      if (sqlAnnotationType != null) {
    
    
        if (sqlProviderAnnotationType != null) {
    
    
          // 两类注解不可同时使用
          throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
        }
        // 含有Select、Insert、Update、Delete 四个注解之一
        Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
        // 取出value值
        final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
        // 基于字符串构建SqlSource
        return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
      } else if (sqlProviderAnnotationType != null) {
    
    
        // 含有SelectProvider、insertProvider、UpdateProvider、DeleteProvider四个注解之一
        Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
        // 根据对应的方法获取SqlSource
        return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
      }
      return null;
    } catch (Exception e) {
    
    
      throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
    }
  }

직접 주석 매핑의 SqlSource 개체는 buildSqlSourceFromStrings 메서드에 의해 생성되고, 간접 주석 매핑의 SqlSource 개체는 ProviderSqlSource 클래스에 의해 생성됩니다.

3.6.3 직접 주석 매핑 분석

직접 주석 매핑은 MapperAnnotationBuilder 개체의 buildSqlSourceFromStrings 메서드에 의해 완료됩니다.

  /**
   * 基于字符串创建SqlSource对象
   * @param strings 字符串,即直接映射注解中的字符串
   * @param parameterTypeClass 参数类型
   * @param languageDriver 语言驱动
   * @return 创建出来的SqlSource对象
   */
  private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
    
    
    final StringBuilder sql = new StringBuilder();
    for (String fragment : strings) {
    
    
      sql.append(fragment);
      sql.append(" ");
    }
    return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
  }

buildSqlSourceFromStrings 메소드의 처리는 매우 간단하며, SQL 문을 설명하는 문자열을 직접 연결하고 처리를 위해 이를 LanguageDriver에 전달합니다.

3.6.4 간접 주석 매핑 분석

간접 주석 매핑의 구문 분석은 ProviderSqlSource에 의해 완료되며, 이를 도입하기 전에 ProviderContext 클래스와 ProviderMethodResolver 클래스라는 두 가지 보조 클래스가 소개됩니다.

  1. ProviderContext
    ProviderContext 클래스는 매우 간단하며 세 가지 속성을 내부적으로 통합합니다. 이 클래스의 기능은 쉽게 전송하고 사용할 수 있도록 세 가지 내부 속성을 전체적으로 통합하는 것입니다.
  // 提供映射信息的类
  private final Class<?> mapperType;
  // 提供映射信息的方法,该方法属于mapperType类
  private final Method mapperMethod;
  // 数据库编号
  private final String databaseId;
  1. ProviderMethodResolver
    ProviderMethodResolver는 기본 메소드인 verifyMethod를 갖는 인터페이스로, 이 메소드의 기능은 @*Provider 주석의 type 속성이 가리키는 클래스에서 메소드 속성에 지정된 메소드를 찾는 것입니다.
  /**
   * 从@*Provider注解的type属性所指向的类中找出method属性中所指的方法
   * @param context 包含@*Provider注解中的type值和method值
   * @return 找出的指定方法
   */
  default Method resolveMethod(ProviderContext context) {
    
    
    // 找出同名方法
    List<Method> sameNameMethods = Arrays.stream(getClass().getMethods())
        .filter(m -> m.getName().equals(context.getMapperMethod().getName()))
        .collect(Collectors.toList());

    // 如果没有找到指定的方法,则@*Provider注解中的type属性所指向的类中不含有method属性中所指的方法。
    if (sameNameMethods.isEmpty()) {
    
    
      throw new BuilderException("Cannot resolve the provider method because '"
          + context.getMapperMethod().getName() + "' not found in SqlProvider '" + getClass().getName() + "'.");
    }
    // 根据返回类型再次判断,返回类型必须是CharSequence类或其子类
    List<Method> targetMethods = sameNameMethods.stream()
        .filter(m -> CharSequence.class.isAssignableFrom(m.getReturnType()))
        .collect(Collectors.toList());
    if (targetMethods.size() == 1) {
    
    
      // 方法唯一,返回该方法
      return targetMethods.get(0);
    }

    if (targetMethods.isEmpty()) {
    
    
      throw new BuilderException("Cannot resolve the provider method because '"
          + context.getMapperMethod().getName() + "' does not return the CharSequence or its subclass in SqlProvider '"
          + getClass().getName() + "'.");
    } else {
    
    
      throw new BuilderException("Cannot resolve the provider method because '"
          + context.getMapperMethod().getName() + "' is found multiple in SqlProvider '" + getClass().getName() + "'.");
    }
  }

지정된 메서드를 찾는solveMethod 프로세스는 주로 두 단계로 나뉩니다.

  • 첫 번째 단계는 메소드 이름과 일치하는 모든 메소드를 찾는 것입니다.
  • 두 번째 단계는 메서드의 반환 값을 기반으로 추가 검증을 수행하는 것입니다.

인터페이스의 소스코드를 읽고 분석할 때, 인터페이스의 기본 메소드에서 이에 대한 참조를 반드시 주의하시기 바랍니다. ResolveMethod 메소드에서 이는 ProviderMethodResolver 인터페이스가 아닌 메소드를 호출하는 엔터티 객체를 참조합니다.

// 找出同名方法
    List<Method> sameNameMethods = Arrays.stream(getClass().getMethods())
        .filter(m -> m.getName().equals(context.getMapperMethod().getName()))
        .collect(Collectors.toList());

위 코드에 포함된 "getClass().getMethods()" 문은 "this.getClass().getMethods()"로 작성할 수 있습니다. ResolveMethod 메서드를 호출하는 문은 ProviderSqlSource 클래스의 생성자입니다.

  if (providerMethodName.length() == 0 && ProviderMethodResolver.class.isAssignableFrom(this.providerType)) {
    
    
        this.providerMethod = ((ProviderMethodResolver) this.providerType.getDeclaredConstructor().newInstance())
            .resolveMethod(new ProviderContext(mapperType, mapperMethod, configuration.getDatabaseId()));
      }

따라서,solveMethod 메소드의 this는 공급자 유형 객체를 참조하는 "this.providerType.getDeclaredConstructor().newInstance()"를 참조합니다. 공급자 유형의 지정 문을 추가로 분석하면 공급자 유형이 @*Provider 주석의 유형 속성이 가리키는 클래스의 인스턴스를 참조한다는 결론에 도달할 수 있습니다.

3.6.5 ProviderSqlSource 클래스

// SqlSource的子类,能够根据*Provider的信息初始化得到
  // 调用入口唯一,在MapperAnnotationBuilder:getSqlSourceFromAnnotations中
public class ProviderSqlSource implements SqlSource {
    
    
  // Configuration对象
  private final Configuration configuration;
  // *Provider注解上type属性所指的类
  private final Class<?> providerType;
  // 语言驱动
  private final LanguageDriver languageDriver;
  // 含有注解的接口方法
  private final Method mapperMethod;
  // *Provider注解上method属性所指的方法
  private Method providerMethod;
  // 给定SQL语句的方法对应的参数
  private String[] providerMethodArgumentNames;
  // 给定SQL语句的方法对应的参数类型
  private Class<?>[] providerMethodParameterTypes;
  // ProviderContext对象
  private ProviderContext providerContext;
  // ProviderContext编号
  private Integer providerContextIndex;
}  

SqlSource 인터페이스의 하위 클래스인 ProviderSqlSource 클래스는 getBoundSql 메서드(SqlSource 인터페이스의 추상 메서드)를 구현합니다. 구현 프로세스는 getBoundSql 및 createSqlSource 두 가지 메소드에 포함되어 있습니다.

  /**
   * 获取一个BoundSql对象
   * @param parameterObject 参数对象
   * @return BoundSql对象
   */
  public BoundSql getBoundSql(Object parameterObject) {
    
    
    // 获取SqlSource对象
    SqlSource sqlSource = createSqlSource(parameterObject);
    // 从SqlSource中获取BoundSql对象
    return sqlSource.getBoundSql(parameterObject);
  }

  /**
   * 获取一个BoundSql对象
   * @param parameterObject 参数对象
   * @return SqlSource对象
   */
  private SqlSource createSqlSource(Object parameterObject) {
    
    
    try {
    
    
      // SQL字符串信息
      String sql;
      if (parameterObject instanceof Map) {
    
     // 参数是Map
        int bindParameterCount = providerMethodParameterTypes.length - (providerContext == null ? 0 : 1);
        if (bindParameterCount == 1 &&
          (providerMethodParameterTypes[Integer.valueOf(0).equals(providerContextIndex) ? 1 : 0].isAssignableFrom(parameterObject.getClass()))) {
    
    
          // 调用*Provider注解的type类中的method方法,从而获得SQL字符串
          sql = invokeProviderMethod(extractProviderMethodArguments(parameterObject));
        } else {
    
    
          @SuppressWarnings("unchecked")
          Map<String, Object> params = (Map<String, Object>) parameterObject;
          // 调用*Provider注解的type类中的method方法,从而获得SQL字符串
          sql = invokeProviderMethod(extractProviderMethodArguments(params, providerMethodArgumentNames));
        }
      } else if (providerMethodParameterTypes.length == 0) {
    
    
        // *Provider注解的type类中的method方法无需入参
        sql = invokeProviderMethod();
      } else if (providerMethodParameterTypes.length == 1) {
    
    
        if (providerContext == null) {
    
    
          // *Provider注解的type类中的method方法有一个入参
          sql = invokeProviderMethod(parameterObject);
        } else {
    
    
          // *Provider注解的type类中的method方法入参为providerContext对象
          sql = invokeProviderMethod(providerContext);
        }
      } else if (providerMethodParameterTypes.length == 2) {
    
    
        sql = invokeProviderMethod(extractProviderMethodArguments(parameterObject));
      } else {
    
    
        throw new BuilderException("Cannot invoke SqlProvider method '" + providerMethod
          + "' with specify parameter '" + (parameterObject == null ? null : parameterObject.getClass())
          + "' because SqlProvider method arguments for '" + mapperMethod + "' is an invalid combination.");
      }
      Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
      // 调用languageDriver生成SqlSource对象
      return languageDriver.createSqlSource(configuration, sql, parameterType);
    } catch (BuilderException e) {
    
    
      throw e;
    } catch (Exception e) {
    
    
      throw new BuilderException("Error invoking SqlProvider method '" + providerMethod
          + "' with specify parameter '" + (parameterObject == null ? null : parameterObject.getClass()) + "'.  Cause: " + extractRootCause(e), e);
    }
  }

전체 구현 프로세스는 다음 세 단계로 요약될 수 있습니다.

  1. SQL 문자열을 얻으려면 *Provider로 주석이 달린 유형 클래스의 메소드 메소드를 호출하십시오.
  2. SQL 문자열 및 기타 매개변수를 LanguageDriver의 createSqlSource 메소드에 전달하여 새 SqlSource 객체를 생성합니다.
  3. 새로 생성된 SqlSource 개체의 getBoundSql 메서드를 호출하여 BoundSql 개체를 가져옵니다.

추천

출처blog.csdn.net/d495435207/article/details/130574275