1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/topanda-mybatis-3

Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Путешествие начинающего разработчика в MyBatis: анализ исходного кода

Найти точку входа для изучения исходного кода MyBatis

Если мы будем использовать MyBatis в виде простого кода, наш код будет выглядеть примерно так:

        // Определение основного конфигурационного файла MyBatis
        String resource = "resources/config-custom-s1.xml";
        // Получение входного потока конфигурационного файла MyBatis
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream(resource);
        // Создание объекта SqlSessionFactory с помощью входного потока
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // Получение объекта SqlSession с помощью SqlSessionFactory
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // Выполнение операций
        // Методы userDao будут заменены на прокси
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User teacher = userDao.getById(1);
        System.out.println(userDao.insert(2, "panda"));

        sqlSession.rollback();
        // Закрытие сессии
        sqlSession.close();

Сначала мы инициализируем объект SqlSessionFactory с помощью основного конфигурационного файла MyBatis, затем используем этот объект SqlSessionFactory для получения объекта SqlSession, и в этой сессии получаем интерфейс, который мы хотим использовать для выполнения операций DAO.

Прежде чем углубляться в детали реализации, давайте сначала познакомимся с тремя основными классами:

  • SqlSessionFactoryBuilder
  • SqlSessionFactory
  • SqlSessionВ 23 основных паттернах проектирования есть паттерн, называемый паттерном построителя, который обычно используется для создания сложных объектов. В данном случае, SqlSessionFactoryBuilder является простым примером этого паттерна, его задача — создать объект SqlSessionFactory.На самом деле, инициализация объекта SqlSessionFactory в MyBatis — это сложный процесс, но для упрощения понимания мы можем пренебречь этим процессом и просто запомнить, что SqlSessionFactoryBuilder — это инструмент, который упрощает создание объекта SqlSessionFactory.

Из названия класса SqlSessionFactory можно легко догадаться, что это фабрика для создания объектов SqlSession. Когда мы говорим о фабриках, мы имеем в виду паттерн проектирования фабрики, который позволяет использовать методы фабрики вместо обычного создания объектов с помощью оператора new. Это также может упростить некоторые процессы создания, хотя здесь мы только коснемся этого паттерна, не углубляясь в детали, так как изучение исходного кода MyBatis — это наш основной приоритет.

SqlSession

Объект SqlSession создается с помощью SqlSessionFactory. Этот объект SqlSession является первым настоящим объектом, который нам нужно изучить.

Мы знаем, что для выполнения операций с базой данных нам сначала нужно получить соединение с базой данных, а затем уже выполнять операции.

В фреймворке MyBatis операции с JDBC были упакованы и унифицированы, и мы можем рассматривать этот API как объект Connection в MyBatis. Конкретная форма этого API — это объект SqlSession.Однако, в отличие от Connection, объект SqlSession не только упаковывает операции с JDBC, но и предоставляет другие полезные функции, такие как метод SqlSession#getMapper(), который принимает тип интерфейса DAO и возвращает соответствующий объект. При вызове методов этого объекта автоматически выполняются операции с JDBC.Как реализуется эта функция? Это связано с основными принципами работы Mybatis, но вкратце можно сказать, что реализация основана на паттерне проектирования прокси. Этот паттерн является одним из часто используемых паттернов проектирования, который позволяет заменить объект-цель и выполнять операции вместо него. В случае с JDBC, определенные операции DAO фактически не существуют, но благодаря прокси-объекту они выглядят как экземпляры интерфейсов DAO и выполняют операции с атрибутами JDBC.

Но когда Mybatis создает прокси для интерфейсов DAO? Ответ на этот вопрос мы найдем, вернувшись к объекту SqlSessionFactoryBuilder и рассмотрев его.

SqlSessionFactoryBuilder

SqlSessionFactoryBuilder кажется простым интерфейсом, который определяет метод build с несколькими перегрузками.

SqlSessionFactory build(Reader reader);
SqlSessionFactory build(Reader reader, String environment);
SqlSessionFactory build(Reader reader, Properties properties);
SqlSessionFactory build(Reader reader, String environment, Properties properties);
SqlSessionFactory build(InputStream inputStream);
SqlSessionFactory build(InputStream inputStream, String environment);
SqlSessionFactory build(InputStream inputStream, Properties properties);
SqlSessionFactory build(InputStream inputStream, String environment, Properties properties);
SqlSessionFactory build(Configuration config);

Перегруженные методы SqlSessionFactory самые сложные из двух.

  • Один из них SqlSessionFactory build(InputStream inputStream, String environment, Properties properties);
  • Другой SqlSessionFactory build(Reader reader, String environment, Properties properties);Эти два метода очень похожи, различие заключается только в конкретном типе потока файлов: один использует Reader, другой — InputStream. Основное различие отсутствует, поэтому здесь мы рассмотрим метод build, соответствующий InputStream. К слову, мы можем преобразовать InputStream в Reader с помощью Reader reader = new InputStreamReader(inputStream);.
     /**
     * Создает объект SqlSessionFactory
     *
     * @param inputStream байтовый поток конфигурационного файла Mybatis
     * @param environment имя по умолчанию для окружения, конфигурация окружения может быть найдена в узле Environments конфигурационного файла Mybatis, по умолчанию "default"
     * @param properties  определенные параметры
     */
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // Создает парсер основного конфигурационного файла Mybatis
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // Использует по умолчанию JDBC сессионный фабрику
            return build(
                    parser.parse()/*Парсит конфигурационный файл Mybatis*/
            );
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Ошибка при создании SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                inputStream.close();
            } catch (IOException e) {
                // Намеренно игнорируем. Предпочтение отдается предыдущей ошибке.
            }
        }
    }

В методе build создается объект XMLConfigBuilder, который фактически является конкретной реализацией абстрактного класса BaseBuilder. Основная цель этого объекта — парсить основной конфигурационный файл Mybatis.С точки зрения определения, BaseBuilder должен был быть предназначен для унификации входа в парсинг и построение компонентов Mybatis, но в реальности его конкретная реализация не следует этой концепции. Кроме того, я не очень люблю название *Builder, так как оно звучит как название для построения сложного объекта, но на самом деле он больше занимается парсингом XML-конфигураций, чем построением объектов. Поэтому я предпочитаю названия вроде *Parser, которые используются только для парсинга, а конкретные операции построения доверены *Register. Конечно, это только мое личное мнение.

Продолжая вернуться к коду, когда мы вызываем метод XMLConfigBuilder#parse(), он возвращает объект Configuration, который передаётся методу SqlSessionFactory build(Configuration config) для дальнейшей обработки. Ну что ж, вышло так, что конечная реализация заключается в вызове одноаргументного метода.

    /**
     * Создаёт объект SqlSessionFactory
     *
     * @param config конфигурационный объект Mybatis
     */
    public SqlSessionFactory build(Configuration config) {
        // Создаётся объект DefaultSqlSessionFactory
        return new DefaultSqlSessionFactory(config);
    }

Этот метод довольно прост, он просто создаёт экземпляр DefaultSqlSessionFactory с помощью объекта Configuration.Теперь, пропустив метод SqlSessionFactory build(Configuration config), мы снова обращаемся к XmlConfigBuilder:

// Создаётся парсер конфигурационного файла Mybatis
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// Используется по умолчанию JDBC-сессионный фабрикатор
return build(
        parser.parse() /*Парсинг конфигурационного файла Mybatis*/
);

Здесь вызывается конструктор XMLConfigBuilder(InputStream inputStream, String environment, Properties props) для инициализации экземпляра XmlConfigBuilder, а затем используется метод #parse для создания объекта Configuration, который используется для конфигурации экземпляра DefaultSqlSessionFactory.Очевидно, что в методе XmlConfigBuilder#parse() выполняются некоторые неизвестные нам методы, которые используются для парсинга XML-конфигурационного файла Mybatis и создания объекта конфигурации Mybatis.


Отслеживая код в конструкторе XMLConfigBuilder# XMLConfigBuilder(InputStream inputStream, String environment, Properties props):

    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(
                        inputStream, /*Входной поток XML-файла*/
                        true, /*Включение валидации*/
                        props, /*Свойства*/
                        new XMLMapperEntityResolver() /*Фабрика XML-сущностей*/
                ) /*Создание экземпляра парсера XML*/
                , environment /*Объект окружения*/
                , props /*Свойства*/
        );
    }

Вот MyBatis использует входной поток XML-файла для создания объекта XPathParser. Функция XPathParser довольно проста: он использует SAX для парсинга XML-документа и создания DOM-дерева, которое затем сохраняется в объекте XPathParser. Ниже приведены некоторые части кода класса XPathParser:

public class XPathParser {
    /**
     * Содержимое XML-документа
     */
    private final Document document;
    /**
     * Проверка DTD
     */
    private boolean validation;
    /**
     * Резолвер сущностей
     */
    private EntityResolver entityResolver;
    /**
     * Свойства
     */
    private Properties variables;
    /**
     * Объект XPath
     */
    private XPath xpath;
    
}

Конструктор, который мы только что вызвали, выглядит следующим образом:

public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(reader));
}
```В этом конструкторе есть две части: вызов `commonConstructor` для инициализации текущих свойств и вызов `createDocument` для парсинга DOM-дерева, соответствующего XML-файлу.

Конструктор `commonConstructor` довольно прост, его код представлен ниже:```java
 /**
  * Общий конструктор для инициализации свойств
  *
  * @param validation     Проверка DTD
  * @param variables      Свойства
  * @param entityResolver Резолвер сущностей
  */
 private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
     // Проверка DTD
     this.validation = validation;
     // Конфигурация резолвера сущностей
     this.entityResolver = entityResolver;
     // Свойства MyBatis
     this.variables = variables;
 }
 ``````md
 ## Инициализация XPath
 ```java
 // Инициализация XPath
 XPathFactory factory = XPathFactory.newInstance();
 this.xpath = factory.newXPath();
### Метод `createDocument`
Метод `createDocument`主要用于根据当前配置解析XML文件并获取DOM对象:
```java
/**
 * Создает текстовый объект на основе входного источника.
 *
 * @param inputSource входной источник
 * @return текстовый объект
 */
private Document createDocument(InputSource inputSource) {
    // важно: это должно быть вызвано ТОЛЬКО после общего конструктора
    try {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        // проверка, должна ли быть валидация документа, который будет анализироваться
        factory.setValidating(validation);
        // поддержка XML-пространств имен
        factory.setNamespaceAware(false);
        // игнорирование комментариев
        factory.setIgnoringComments(true);
        // игнорирование пробельных символов
        factory.setIgnoringElementContentWhitespace(false);
        // преобразование узлов CDATA в узлы TEXT
        factory.setCoalescing(false);
        // разворачивание узлов с ссылками на сущности
        factory.setExpandEntityReferences(true);
    }
```            DocumentBuilder builder = factory.newDocumentBuilder();
            // Настройка парсера XML-документа, в основном это XMLMapperEntityResolver
            builder.setEntityResolver(entityResolver);
``````java
setEntityResolver(entityResolver);
            // Настройка обработчика ошибок
            builder.setErrorHandler(new ErrorHandler() {
                @Override
                public void error(SAXParseException exception) throws SAXException {
                    throw exception;
                }
            });
```                @Override
               public void fatalError(SAXParseException exception) throws SAXException {
                   throw exception;
               }

               @Override
               public void warning(SAXParseException exception) throws SAXException {
               }
           });
           // Парсинг DOM-дерева
           return builder.parse(inputSource);
       } catch (Exception e) {
           throw new BuilderException("Ошибка при создании экземпляра документа. Причина: " + e, e);
       }
   }

После выполнения этих двух методов процесс конструирования XPathParse завершается. В этот момент он уже содержит объект Document и доступ к XPath-представлению, и последующие операции будут использовать XPath для чтения содержимого объекта Document.

Вернемся к конструктору XMLConfigBuilder. Мы уже упоминали, что объект XMLConfigBuilder является подклассом BaseBuilder. В классе BaseBuilder есть три final-атрибута:

public abstract class BaseBuilder {
    /**
     * Конфигурация MyBatis
     */
    protected final Configuration configuration;
    /**
     * Регистр типовых псевдонимов
     */
    protected final TypeAliasRegistry typeAliasRegistry;
    /**
     * Регистр типовых обработчиков
     */
    protected final TypeHandlerRegistry typeHandlerRegistry;
  • Атрибут Configuration представляет конфигурацию MyBatis, в которой хранятся различные настройки для использования MyBatis в процессе работы. Мы не будем подробно останавливаться на этом сейчас, а рассмотрим его при конкретном использовании.- Атрибут TypeAliasRegistry представляет регистр типовых псевдонимов Mybatis, который используется для упрощения кода при работе с Mybatis. Например, при определении resultMap можно использовать <result ... javaType="int"/>, вместо полного имени класса java.lang.Integer. Реализация TypeAliasRegistry сама по себе очень проста. Она поддерживает MAP-коллекцию, названную Map<String, Class<?>> TYPE_ALIASES, которая используется для хранения отображения между именами классов и их определениями. При этом в конструкторе по умолчанию регистрируются некоторые часто используемые альтернативные названия классов.

  • Атрибут TypeHandlerRegistry содержит регистр типовых обработчиков преобразования типов Mybatis. Типовые обработчики (TypeHandler) предназначены для выполнения преобразований между Java типами и JDBC типами, а также инициализируются некоторыми часто используемыми типовыми обработчиками при создании.XMLConfigBuilder также определяет несколько свойств, которые выполняют следующие функции:

    /*Флаг парсинга, предотвращающий повторное парсинг*/
    private boolean parsed;
    /*XML адрес парсера*/
    private final XPathParser parser;
    /*Указывает текущую среду выполнения Mybatis*/
    private String environment;

Здесь свойство environment используется для указания текущей среды выполнения Mybatis, что будет подробно объяснено в дальнейшем процессе парсинга. ```Возвращаясь к конструктору XMLConfigBuilder, после инициализации объекта `XPathParser`, вызывается перегруженный конструктор `XMLConfigBuilder(XPathParser parser, String environment, Properties props)`:

    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        // Инициализация объекта Configuration и регистрация некоторых псевдонимов и языковых драйверов
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        // Установка переменных MyBatis
        this.configuration.setVariables(props);
        // Инициализация флага парсинга
        this.parsed = false;
        // Инициализация среды выполнения
        this.environment = environment;
        // Инициализация парсера XML адреса
        this.parser = parser;
    }

Важно отметить, что в этом конструкторе вызывается конструктор родительского класса:

  public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        // Синхронизация таблицы псевдонимов из конфигурации MyBatis
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        // Синхронизация таблицы типовых обработчиков из конфигурации MyBatis
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }

Из кода видно, что BaseBuilder#typeAliasRegistry и BaseBuilder#typeHandlerRegistry ссылаются на свойства из конфигурации MyBatis. Здесь стоит обратить внимание:> BaseBuilder#typeAliasRegistry и Configuration#typeAliasRegistry являются одним и тем же экземпляром.

BaseBuilder#typeHandlerRegistry и Configuration#typeHandlerRegistry являются одним и тем же экземпляром. До этого момента процесс инициализации XMLConfigBuilder был полностью завершен.

Далее MyBatis вызывает метод XmlConfigBuilder#parse() в позиции SqlSessionFactoryBuilder#build()>return build(parser.parse()/*парсинг конфигурации MyBatis*/);, что запускает весь процесс инициализации и парсинга MyBatis.

    public Configuration parse() {
        if (parsed) {
            // Второй вызов XMLConfigBuilder
            throw new BuilderException("Каждый экземпляр XMLConfigBuilder может быть использован только один раз.");
        }
        // Сброс флага парсинга XMLConfigBuilder для предотвращения повторного парсинга
        parsed = true;

        // Начало парсинга конфигурационного файла MyBatis
        // Парсинг конфигурационного файла, чтение содержимого узла [configuration]
        parseConfiguration(parser.evalNode("/configuration"));

        // Возврат экземпляра конфигурации MyBatis
        return configuration;
    }

Сначала вызывается метод #parseConfiguration() для парсинга узла configuration основного конфигурационного файла MyBatis, а данные узла configuration получены с помощью parser.evalNode("/configuration"), что в конечном итоге приводит к методу:

    private Object evaluate(String expression, Object root, QName returnType) {
        try {
            // Вычисление XPath-выражения в указанном контексте и возврат результата в виде указанного типа.
            return xpath.evaluate(expression, root, returnType);
        } catch (Exception e) {
            throw new BuilderException("Ошибка вычисления XPath. Причина: " + e, e);
        }
    }
```Затем объект передается методом `return new XNode(this, node, variables);` для упаковки в объект типа `XNode`.

Основные определения класса `XNode` следующие:

public class XNode { /**

  • Узел DOM / private final Node node; /*
  • Имя текущего узла / private final String name; /*
  • Содержимое узла / private final String body; /*
  • Атрибуты узла / private final Properties attributes; /*
  • Конфигурация MyBatis / private final Properties variables; /*
  • Парсер XPathParser */ private final XPathParser xpathParser;

}


Основная роль этого класса заключается в упрощении доступа к ресурсам DOM.

После упаковки содержимого узла `configuration` в объект типа `XNode`, начинается настоящий процесс парсинга.## Анализ основного конфигурационного файла MyBatis и настройка MyBatis для создания необходимой среды

`configuration` является корневым узлом основного конфигурационного файла MyBatis, и мы обычно используем его следующим образом:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
```<configuration>
 . . .
 </configuration>

Ссылаясь на DTD-определение MyBatis, можно заметить, что в узле configuration допускаются 11 типов дочерних узлов, и все они являются необязательными. Это означает, что MyBatis имеет по умолчанию обработку для этих свойств:

<! ELEMENT configuration (
properties?
, settings?
, typeAliases?
, typeHandlers?
, objectFactory?
, objectWrapperFactory?
, reflectorFactory?
, plugins?
, environments?
, databaseIdProvider?
, mappers?
)>

Каждый из этих дочерних узлов имеет определенное назначение, которое мы рассмотрим подробнее в процессе анализа. Продолжим анализ метода parseConfiguration:

/**
 * Разбор узла Configuration
 */
private void parseConfiguration(XNode root) {
    try {
        //issue #117 read properties first
        // Загрузка конфигурационных файлов и перезапись соответствующих свойств [узел properties]
        propertiesElement(root.evalNode("properties"));
        // Преобразование содержимого узла settings в Property и проверка
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        // Определение способа доступа к файлам ресурсов на основе конфигурации settings
        loadCustomVfs(settings);
        // Определение способа обработки логов на основе конфигурации settings
        loadCustomLogImpl(settings);
        // Разбор псевдонимов
        typeAliasesElement(root.evalNode("typeAliases"));
        // Конфигурация плагинов
        pluginElement(root.evalNode("plugins"));
        // Создание фабрики для создания объектов
        objectFactoryElement(root.evalNode("objectFactory"));
        // Создание фабрики для обертывания объектов
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        // Создание фабрики для рефлексии
       reflectorFactoryElement(root.evalNode("reflectorFactory"));
       // Инициализация глобальной конфигурации на основе конфигурации settings
       settingsElement(settings);
       // Чтение конфигурации среды и поиск соответствующего менеджера транзакций и источника данных для текущей среды (по умолчанию default)
       environmentsElement(root.evalNode("environments"));
       // Создание класса для маркера типа базы данных, MyBatis загружает все заявки без databaseId и с текущим databaseId, заявки с databaseId имеют более высокий приоритет
       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
       // Регистрация конвертеров типов
       typeHandlerElement(root.evalNode("typeHandlers"));
       // Регистрация парсинга файлов MapperXml, соответствующих Dao
       mapperElement(root.evalNode("mappers"));
   } catch (Exception e) {
       throw new BuilderException("Ошибка при разборе конфигурации SQL Mapper. Причина: " + e, e);
   }
}

Разбор конфигурационных свойств MyBatis (узел properties)

В процессе разбора сначала загружается узел configuration -> properties, и соответствующие свойства перезаписываются в конфигурации MyBatis. Определение свойств properties очень простое:

<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>

Оно позволяет иметь несколько элементов property, а также настраивать атрибуты resource и url. Однако, атрибуты resource и url не могут существовать одновременно. Определение элемента property выглядит следующим образом:

<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>
```Элемент `property` состоит из `name` и `value`, поэтому его можно интерпретировать как свойство.
В процессе конкретного анализа можно заметить, что Mybatis предоставляет соответствующие операции анализа для этих трех различных методов конфигурации.

/**

  • Анализ элемента Properties
  • @param context узел Properties
  • @throws Exception исключение */ private void propertiesElement(XNode context) throws Exception { if (context != null) { // Получаем значения атрибутов name и value элементов Property и преобразуем их в Properties Properties defaults = context.getChildrenAsProperties(); // Получаем ссылку на ресурс String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); if (resource != null && url != null) { throw new BuilderException("Элемент свойства не может одновременно указывать на URL и файл конфигурации на основе ресурса. Пожалуйста, укажите одно или другое."); } } }
                 // Загрузка файла конфигурации *. Properties
                 defaults.putAll(Resources.getResourceAsProperties(resource));
             } else if (url != null) {
                 // Загрузка файла конфигурации *. Properties
                 defaults.putAll(Resources.getUrlAsProperties(url));
             }
             Properties vars = configuration.getVariables();
             if (vars != null) {
                 // Загрузка конфигурационных данных
                 defaults.putAll(vars);
             }
             // Обновление конфигурационных данных и синхронизация глобальных значений по умолчанию в парсер
             parser.setVariables(defaults);
             // Синхронизация в настройках MyBatis
             configuration.setVariables(defaults);
         }
     }

MyBatis сначала получает все элементы property из секции properties и на их основе создает объект Properties. Реализация метода context.getChildrenAsProperties(); выглядит следующим образом:

  /**
   * Получение всех элементов settings из секции settings, где setting>name является ключом для Properties,
   * а setting>value — значением.
   */
  public Properties getChildrenAsProperties() {
    Properties properties = new Properties();
    for (XNode child : getChildren()) {
      String name = child.getStringAttribute("name");
      String value = child.getStringAttribute("value");
      if (name != null && value != null) {
        properties.setProperty(name, value);
      }
    }
    return properties;
  }

Затем MyBatis получает атрибуты resource и url из секции properties, проверяет, чтобы они не были указаны одновременно, и загружает соответствующий файл конфигурации на основе указанных атрибутов resource или url. Затем загруженные данные сохраняются в объект Properties.Затем MyBatis получает все переменные из конфигурационного объекта Configuration и сохраняет их в объект Properties. В конце концов, с использованием объединенного объекта Properties обновляются конфигурационные данные в парсере XmlPathParser и в конфигурационном объекте Configuration.С учетом вышеописанного порядка загрузки можно понять, что при конфигурации свойств MyBatis приоритеты следующие:

  1. Параметры, заданные при запуске
  2. Конфигурационные данные, указанные в атрибутах url или resource в основном конфигурационном файле MyBatis
  3. Конфигурационные данные, указанные в элементах property в основном конфигурационном файле MyBatis.### Параметры конфигурации Mybatis

После парсинга узла properties, следует парсинг узла settings.

  // Преобразование содержимого узла settings в Property и проверка.
  Properties settings = settingsAsProperties(root.evalNode("settings"));

Определение узла settings:

<!ELEMENT setting EMPTY>
<!ATTLIST setting
name CDATA #REQUIRED
value CDATA #REQUIRED
>

Это определяет, что в узле settings должны быть один или более узлов setting, каждый из которых должен иметь два параметра: name и value. Узел setting не может иметь дополнительных дочерних узлов.

Из этого DTD также следует, что узел settings будет преобразован в объект Properties.

Код для парсинга settings:

  /**
   * Парсинг узла settings
   *
   * @param context узел settings
   */
    private Properties settingsAsProperties(XNode context) {
        if (context == null) {
            // Если узел settings не указан, вернуть пустой объект Properties
            return new Properties();
        }

        Properties props = context.getChildrenAsProperties();
    }
```        // Проверка, что все параметры settings поддерживаются конфигурацией
        // Проверка, что все параметры settings известны классу Configuration
        // Получение объекта MetaClass для класса Configuration
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
        for (Object key : props.keySet()) {
            if (!metaConfig.hasSetter(String.valueOf(key))) {
                // Проверка поддержки параметров settings
                throw new BuilderException("Параметр конфигурации " + key + " неизвестен. Убедитесь, что вы правильно его написали (чувствительность к регистру).");
            }
        }
        return props;
    }При парсинге узла `settings` выполняется несколько действий. Сначала весь узел `settings` преобразуется в объект `Properties`. Если узел `settings` не указан, возвращается пустой объект `Properties`. Если узел `settings` указан, проверяется, что все параметры поддерживаются конфигурацией.

В этом процессе используется новый объект `MetaClass`, который Mybatis использует для хранения метаданных типа объекта. У этого объекта есть два основных атрибута: фабрика рефлексии и объект, описывающий метаданные атрибутов.

```java
public class MetaClass {
    /**
     * Рефлексный фабрикатор
     */
    private final ReflectorFactory reflectorFactory;
    /**
     * Метаданные для заданного класса
     */
    private final Reflector reflector;

}

В данном контексте ReflectorFactory используется для чтения class и генерации Reflector, который является вспомогательным фабрикатором. Reflector кэширует базовую информацию о классе, включая тип класса, имена читаемых и записываемых свойств, а также соответствующие методы getter и setter, конструкторы и т.д.

В этом случае XmlConfigBuilder использует DefaultReflectorFactory для получения базовых свойств Configuration и проверяет, есть ли соответствующие методы setter для свойств конфигурации settings.Таким образом, весь процесс анализа settings завершен. Дальнейшие действия заключаются в определении способа доступа MyBatis к ресурсам внутри JAR-файла на основе конфигурации settings. В MyBatis определён абстрактный класс VFS, который задаёт API для доступа к ресурсам контейнера. По умолчанию имеются два реализации этого класса: JBoss6VFS.class и DefaultVFS.class.```java // Определение способа доступа к ресурсам на основе конфигурации settings loadCustom_vfs(settings);


```java
/**
 * Загрузка реализации класса для доступа к системному виртуальному файловому системе
 *
 * @param props глобальные системные настройки
 * @throws ClassNotFoundException если реализация класса не найдена
 */
private void loadCustom_vfs(Properties props) throws ClassNotFoundException {
    // Получение настроек vfsImpl из props, если они существуют, они перекрывают стандартные реализации
    String value = props.getProperty("vfsImpl");
    if (value != null) {
        String[] clazzes = value.split(",");
        for (String clazz : clazzes) {
            if (!clazz.isEmpty()) {
                @SuppressWarnings("unchecked")
                Class<? extends VFS> vfsImpl = (Class<? extends VFS>) Resources.classForName(clazz);
                // Обновление реализации доступа к виртуальной файловой системе MyBatis
                configuration.setVfsImpl(vfsImpl);
            }
        }
    }
}

В приведенном выше коде метод configuration.setVfsImpl(vfsImpl) не просто устанавливает значение, но и вызывает метод VFS.addImplClass(this.vfsImpl):

/**
 * Установка нового класса VFS
 * @param vfsImpl класс VFS
 */
public void setVfsImpl(Class<? extends VFS> vfsImpl) {
    if (vfsImpl != null) {
        this.vfsImpl = vfsImpl;
        // Добавление нового класса VFS
        VFS.addImplClass(this.vfsImpl);
    }
}
```Метод `VFS.addImplClass(this.vfsImpl)` добавляет новый класс реализации VFS в `VFS#USER_IMPLEMENTATIONS`, что позволяет при получении экземпляра VFS использовать этот новый класс в приоритетном порядке. После завершения обновления класса реализации Mybatis для доступа к виртуальной файловой системе, продолжается конфигурация класса реализации логирования Mybatis в соответствии с параметрами `settings`.

// Определяет метод обработки логирования в зависимости от настроек loadCustomLogImpl(settings);

/**

  • Загрузка пользовательского класса реализации логирования
  • @param props глобальные параметры */ private void loadCustomLogImpl(Properties props) { Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl")); configuration.setLogImpl(logImpl); }

Только после этого завершается обработка параметров `settings` на данном этапе.

### Разбор механизма псевдонимов типов Mybatis (узел typeAliases)
Следующим этапом является обработка регистрации псевдонимов типов Mybatis. Ранее было упомянуто, что псевдонимы типов используются для упрощения кода при использовании Mybatis.Для настройки своих псевдонимов в MyBatis используются элементы `typeAliases`, определение которых представлено ниже:
В элементе `typeAliases` могут быть указаны ноль или более элементов `typeAlias`/`package`, которые не могут содержать дополнительные подэлементы. Вот результат перевода:

В разделе `typeAliases`:
- Внутри тега `typeAlias` можно указать два параметра: `type` и `alias`. Параметр `type` должен указывать полное имя типа Java, и он является обязательным. Параметр `alias` представляет собой псевдоним для объекта Java и является необязательным. По умолчанию используется имя класса Java, полученное с помощью `#getSimpleName()`.
- Внутри тега `package` можно указать один обязательный параметр `name`, который должен указывать имя пакета Java. Все классы в этом пакете, соответствующие определенным правилам (по умолчанию это подклассы `Object.class`), будут зарегистрированы.```java
/**
 * Парсинг конфигурации для узла typeAliases
 *
 * @param parent узел typeAliases
 */
private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                // Регистрация псевдонимов для пакета, по умолчанию используется имя класса Java
                String typeAliasPackage = child.getStringAttribute("name");
                // Регистрация псевдонимов в таблице псевдонимов
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
                // Обработка конфигурации typeAlias и регистрация псевдонимов и типов в таблице псевдонимов
                String alias = child.getStringAttribute("alias");
                String type = child.getStringAttribute("type");
                try {
                    Class<?> clazz = Resources.classForName(type);
                    if (alias == null) {
                        typeAliasRegistry.registerAlias(clazz);
                    } else {
                        typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException e) {
                    throw new BuilderException("Ошибка при регистрации typeAlias для '" + alias + "'. Причина: " + e, e);
                }
            }
        }
    }
}
```MyBatis обрабатывает узел `typeAliases` двумя способами: обработка узла `package` и обработка узла `typeAlias`. Обработка пакета начинается с получения базового имени пакета через свойство `name` у объекта `package`. Затем вызывается метод `configuration#getTypeAliasRegistry()#registerAliases()` для регистрации псевдонимов.

> Важно отметить, что ранее было упомянуто, что на самом деле `typeAliasRegistry` в `XmlConfigBuilder` и `configuration` указывают на один и тот же экземпляр. Поэтому вызовы `typeAliasRegistry#registerAliases()` и `configuration#getTypeAliasRegistry()#registerAliases()` не приводят к实质性区别的结果。

Далее рассмотрим метод `TypeAliasRegistry#registerAliases(String)`, который предоставляет возможность массовой регистрации псевдонимов для классов.

/** * Регистрация всех Java-классов в указанном пакете * * @param packageName Имя пакета */ public void registerAliases(String packageName) { registerAliases(packageName, Object.class); }


Вызов переопределенного метода с передачей ограничивающего типа, по умолчанию `Object.class`. Это означает, что будут зарегистрированы псевдонимы для всех подклассов `Object.class` в данном пакете.```
    /**
     * Регистрация псевдонимов для указанного типа и его подтипов
     *
     * @param packageName Имя пакета
     * @param superType   Ограничивающий тип
     */
    public void registerAliases(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        // Возвращение текущего набора найденных классов
        Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
        for (Class<?> type : typeSet) {
            // Игнорирование вложенных классов, интерфейсов (включая package-info.java)
            // Также пропускаем анонимные классы, интерфейсы и вложенные классы. См. проблему #6
            if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
                // Регистрация псевдонима
                registerAlias(type);
            }
        }
    }

В этом методе используется ResolverUtils#find() для получения набора классов, соответствующих ожиданиям. Затем из этого набора исключаются анонимные классы, интерфейсы и вложенные классы, и для оставшихся классов выполняется регистрация псевдонимов.> Метод ResolverUtils#find() используется для рекурсивного поиска всех классов в указанном пакете и его подпакетах, а также для выполнения теста на соответствие каждому найденному классу. Только те классы, которые проходят тест, сохраняются. Код представлен ниже:

     * Рекурсивно просматривает указанный пакет и его подпакеты для классов и выполняет тест Test для всех найденных классов. Только те классы, которые проходят тест, будут сохранены.
     *
     * @param test        Объект теста для фильтрации классов
     * @param packageName Базовое имя пакета для просмотра
     */
    public ResolverUtil<T> find(Test test, String packageName) {
        String path = getPackagePath(packageName);

        try {
            // Получает все файлы в указанном пути
            List<String> children = VFS.getInstance().list(path);
            for (String child : children) {
                if (child.endsWith(".class")) {
                    // Обрабатывает все файлы компиляции классов
                    addIfMatching(test, child);
                }
            }
        } catch (IOException ioe) {
            log.error("Не удалось прочитать пакет: " + packageName, ioe);
        }

        return this;
    }

В этом методе есть строка кода List<String> children = VFS.getInstance().list(path);, где ранее упоминалось, что пользовательские экземпляры VFS становятся активными именно здесь.

   /**
     * Холдер для одиночки
     * Singleton instance holder.
     */
    private static class VFSHolder {
        static final VFS INSTANCE = createVFS();        ```java
        @SuppressWarnings("unchecked")
        static VFS createVFS() {
            // Сначала пробуем реализации учителя, затем встроенные
            List<Class<? extends VFS>> impls = new ArrayList<>();
            // Применяем пользовательские реализации
            impls.addAll(USER_IMPLEMENTATIONS);
            // Применяем системные реализации
            impls.addAll(Arrays.asList((Class<? extends VFS>[]) IMPLEMENTATIONS));
        }
        ```            // Пытаемся использовать каждую реализацию до тех пор, пока не найдется действительная
            VFS vfs = null;
            for (int i = 0; vfs == null || !vfs.isValid(); i++) {
                // Завершаем, когда не удается получить объект vfs или находит действительный объект vfs
``````markdown
                // Получить тип реализации vfs
                Class<? extends VFS> impl = impls.get(i);
                try {
                    // Создать экземпляр vfs
                    vfs = impl.newInstance();
                    if (vfs == null || !vfs.isValid()) {
                        if (log.isDebugEnabled()) {
                            log.debug("Реализация VFS " + impl.getName() +
                                    " недействительна в этой среде.");
                        }
                    }
                } catch (InstantiationException e) {
                    log.error("Не удалось создать экземпляр " + impl, e);
                    return null;
                } catch (IllegalAccessException e) {
                    log.error("Не удалось создать экземпляр " + impl, e);
                    return null;
                }
            }
``````markdown
Процесс инстанцирования объекта `VFS` также использует паттерн проектирования — одиночку (singleton), хотя подробное понимание этого паттерна здесь не требуется.

Вернемся к методу разрешения псевдонимов. После того как все необходимые для регистрации псевдонимов классы из определенного пакета были найдены, MyBatis продолжает обрабатывать эти классы, передавая задачу регистрации псевдонимов методу `registerAlias(Class)`.```java
/**
 * Регистрирует псевдоним для указанного типа в таблице псевдонимов.
 * В случае отсутствия аннотаций используется имя класса в нижнем регистре.
 *
 * @param type Указанный тип
 */
public void registerAlias(Class<?> type) {
    // По умолчанию псевдоним класса — это его простое имя
    String alias = type.getSimpleName();
    // Обработка конфигурации псевдонима из аннотации
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
        alias = aliasAnnotation.value();
    }
    // Регистрация псевдонима
    registerAlias(alias, type);
}
```В этом методе основное внимание уделено получению псевдонима для класса, который требуется зарегистрировать. По умолчанию псевдоним — это простое имя класса, полученное с помощью метода `#getSimpleName()`. Однако, если на классе найдена аннотация `Alias`, значение псевдонима из этой аннотации будет использовано вместо значения по умолчанию.

```После получения псевдонима продолжите вызов метода `registerAlias(String, Class<?>)` для выполнения операции регистрации псевдонима.
/**
 * Регистрирует указанный тип и псевдоним в таблице псевдонимов
 *
 * @param alias псевдоним
 * @param value указанный тип
 */
public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
        throw new TypeException("Параметр alias не может быть null");
    }
    // issue #748
    String key = alias.toLowerCase(Locale.ENGLISH);
    // Проверка на наличие уже зарегистрированного псевдонима
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
        throw new TypeException("Псевдоним '" + alias + "' уже связан со значением '" + TYPE_ALIASES.get(key).getName() + "'. ");
    }
    // Добавление псевдонима в таблицу псевдонимов
    TYPE_ALIASES.put(key, value);
}

При регистрации псевдонима сначала преобразуется псевдоним в строку в нижнем регистре, затем проверяется, существует ли уже зарегистрированный псевдоним или псевдоним с таким же типом. Если ни один из них не существует, псевдоним добавляется в таблицу псевдонимов, то есть в TypeAliasRegistry#TYPE_ALIASES.До этого момента парсинг подэлемента package для тега typeAliases завершён. Теперь перейдём к рассмотрению другого тега typeAlias. Ранее было упомянуто, что typeAlias имеет два атрибута: обязательный атрибут type, который указывает полное имя класса Java, и необязательный атрибут alias, который задаёт псевдоним для класса Java.На самом деле, парсинг тега typeAlias уже включен в процесс парсинга тега package. При парсинге тега typeAlias сначала проверяется наличие класса Java, на который указывает атрибут type. Если класс отсутствует, выбрасывается исключение. Если класс существует, то продолжается обработка значения атрибута alias.

Если для typeAlias задано значение атрибута alias, то вызывается метод registerAlias(String, Class<?>) для регистрации псевдонима. Если значение атрибута alias не задано, то сначала вызывается метод registerAlias(Class), а затем метод registerAlias(String, Class<?>) для регистрации псевдонима после его генерации.

Таким образом, парсинг узла typeAliases завершен. Теперь перейдем к парсингу плагинов MyBatis.

Парсинг плагинов MyBatis (узел plugins)

Плагины MyBatis — это мощная функциональность, которая позволяет вмешиваться в работу MyBatis во время выполнения. Это позволяет выполнять различные действия, такие как популярный плагин пагинации page-helper, который основан на функциональности плагинов MyBatis.

DTD-определение конфигурации плагинов MyBatis выглядит следующим образом:

<!ELEMENT plugins (plugin+)>

<!ELEMENT plugin (property*)>
<!ATTLIST plugin
interceptor CDATA #REQUIRED
>

Тег plugins должен содержать один или более тегов plugin. Тег plugin имеет обязательный атрибут interceptor, который указывает на класс, реализующий интерфейс Interceptor. Внутри тега plugin могут быть указаны ноль или более тегов property, которые задают свойства конфигурации для плагина.Интерфейс Interceptor определен следующим образом:

/**
 * Интерфейс MyBatis-плагина (интерцептора)
 * @author Clinton Begin
 */
public interface Interceptor {
  /**
   * Метод для перехвата выполнения операции
   * @param invocation вызов
   */
  Object intercept(Invocation invocation) throws Throwable;

  /**
   * Метод для перехвата процесса создания объекта
   */
  Object plugin(Object target);

  /**
   * Метод для установки свойств интерцептора
   */
  void setProperties(Properties properties);
}

Затем мы продолжаем анализ конфигурации плагинов Mybatis:

// Конфигурация плагинов
pluginElement(root.evalNode("plugins"));
   /**
     * Парсинг узла plugins
     *
     * @param parent узел plugins
     */
    private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                // Обработка каждого interceptor
                String interceptor = child.getStringAttribute("interceptor");
                // Получение отображения name=>value
                Properties properties = child.getChildrenAsProperties();
                // Получение реализации плагина
                Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
                // Инициализация конфигурации плагина
                interceptorInstance.setProperties(properties);
                // Регистрация плагина, добавление нового экземпляра плагина в цепочку обязанностей плагинов
                configuration.addInterceptor(interceptorInstance);
            }
        }
    }

В процессе работы всего плагина есть два места, которые требуют внимания: один — (Interceptor) resolveClass(interceptor).newInstance();, второй — configuration.addInterceptor(interceptorInstance);.Метод resolveClass(String alias) определен в классе BaseBuilder и используется для разрешения конкретного Java-класса по его псевдониму. Это означает, что мы можем регистрировать плагины MyBatis посредством псевдонимов.

Реализация метода resolveClass(String alias) делегируется методу resolveAlias(String) класса TypeAliasRegistry:

    public <T> Class<T> resolveAlias(String string) {
        try {
            if (string == null) {
                return null;
            }
            // issue #748
            String key = string.toLowerCase(Locale.ENGLISH);
            Class<T> value;
            if (TYPE_ALIASES.containsKey(key)) {
                // загрузка из таблицы псевдонимов
                value = (Class<T>) TYPE_ALIASES.get(key);
            } else {
                // загрузка с помощью рефлексии
                value = (Class<T>) Resources.classForName(string);
            }
            return value;
        } catch (ClassNotFoundException e) {
            throw new TypeException("Не удалось разрешить псевдоним типа '" + string + "'. Причина: " + e, e);
        }
    }

Реализация configuration.addInterceptor(interceptorInstance); также не сводится к простому присваиванию, а включает добавление нового interceptor в существующий список Configuration#interceptorChain#interceptors.

Определение класса InterceptorChain:

/**
 * Цепочка обязанностей MyBatis-плагинов
 *
 * @author Clinton Begin
 */
public class InterceptorChain {

    /**
     * Все плагины
     */
    private final List<Interceptor> interceptors = new ArrayList<>();

    /**
     * Вызов всех методов {@link Interceptor#plugin(Object)} для всех плагинов
     */
    public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
            target = interceptor.plugin(target);
        }
        return target;
    }
}
``````markdown
Таким образом, процесс разбора интерцептора начинается с разбора полного имени класса интерцептора (полное квалифицированное имя или псевдоним), после чего с помощью рефлексии получается объект-экземпляр интерцептора, а затем конфигурационные параметры `property`, указанные в конфигурационном файле, преобразуются в `Properties`, и метод `setProperties(Properties)` интерцептора вызывается для выполнения присваивания. В конце концов, интерцептор регистрируется в `Configuration#interceptorChain#interceptors`.

На этом разбор плагинов MyBatis завершен, и теперь следует разобрать объектный фабрикатор MyBatis.

### Разбор и конфигурация объектного фабрикатора MyBatis (objectFactory)
В MyBatis есть множество операций, которые используют рефлексию для создания объектов, например, при преобразовании возвращаемых результатов JDBC в конкретные сущности, объекты создаются с помощью рефлексии. Для таких операций создания объектов MyBatis предоставляет интерфейс API объектного фабрикатора, называемый `ObjectFactory`, его стандартная реализация — `DefaultObjectFactory`.

Класс `ObjectFactory` определен следующим образом:

/**

  • Фабрика для создания всех объектов в MyBatis

  • @author Clinton Begin */ public interface ObjectFactory {

    /**

    • Установка конфигурационных параметров
    • @param properties конфигурационные параметры */ void setProperties(Properties properties);

    /**

    • Создание экземпляра указанного типа с помощью по умолчанию конструктора
    • @param type указанный тип */ T create(Class type);

    /**

    • Создание экземпляра указанного типа с помощью конструктора и параметров конструктора
    • Создает новый объект с указанным конструктором и параметрами.
    • @param type тип объекта
    • @param constructorArgTypes коллекция типов параметров конструктора
    • @param constructorArgs коллекция параметров конструктора */ T create(Class type, List<Class<?>> constructorArgTypes, List constructorArgs);

    /**

    • Возвращает true, если этот объект может содержать набор других объектов, метод предназначен для совместимости с не-Collection коллекциями.
    • @param type указанный тип объекта
    • @since 3.1.0 */ boolean isCollection(Class type);

}

````configuration -> objectFactoryиспользуется для конфигурации реализации объектного фабрикатора. ```ДляobjectFactory` DTO определен следующим образом:

<!ELEMENT objectFactory (property*)>
<!ATTLIST objectFactory
type CDATA #REQUIRED
>

objectFactory имеет обязательный атрибут type, который используется для указания экземпляра ObjectFactory. В этом атрибуте можно использовать псевдонимы. Внутри objectFactory можно настроить один или несколько элементов property, которые используются для установки параметров экземпляра ObjectFactory.

   // Настройка объекта создания фабрики
   objectFactoryElement(root.evalNode("objectFactory"));
    /**
     * Разбор узла objectFactory
     *
     * @param context узел objectFactory
     */
    private void objectFactoryElement(XNode context) throws Exception {
        if (context != null) {
            // Получение атрибута type узла objectFactory
            String type = context.getStringAttribute("type");
            Properties properties = context.getChildrenAsProperties();
            // Получение экземпляра фабрики
            ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
            factory.setProperties(properties);
            // Настройка объекта создания фабрики
            configuration.setObjectFactory(factory);
        }
    }

Эта реализация также проста: с помощью рефлексии получается экземпляр ObjectFactory, затем свойства, сгенерированные из property, устанавливаются в экземпляр, и в конце концов свойство Configuration#objectFactory устанавливается. Таким образом, разбор тега objectFactory завершен.

Разбор и настройка объекта-обертки фабрики Mybatis (узел objectWrapperFactory)

ObjectWrapperFactory — это фабрика объектов-оберток.В паттерне проектирования существует паттерн, называемый декоратором, который позволяет добавлять дополнительные функции к объекту без изменения его исходного поведения. В данном случае ObjectWrapperFactory используется для создания обертки или декоратора для определенного объекта. ObjectWrapperFactory интерфейс определен следующим образом:

```java
/**
 * Объектный обертчик фабрика,主要用于包装返回的result对象,比如可以用来对返回的数据结果进行统一的操作,比如脱敏,加密等操作。
 *
 * @author Clinton Begin
 * @see org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(ResultSetWrapper, ResultMap, String)
 */
public interface ObjectWrapperFactory {
    /**
     * Объект имеет декоратор
     * @param object Заданный объект
     */
    boolean hasWrapperFor(Object object);
    /**
     * Получить обертку для заданного объекта по метаданным объекта
     * @param metaObject Метаданные объекта
     * @param object Заданный объект
     */
    ObjectWrapper getWrapperFor(MetaObject metaObject, Object object);
}
```}

В Mybatis для `objectWrapperFactory` DTO определен следующим образом:
```markdown
```xml
<!ELEMENT objectWrapperFactory EMPTY>
<!ATTLIST objectWrapperFactory
type CDATA #REQUIRED
>

`objectWrapperFactory` должен иметь атрибут `type`, который указывает на экземпляр `ObjectWrapperFactory`, который можно использовать с помощью псевдонима.
```markdown
```java
// Конфигурация объектного обертчика фабрики
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
```markdown
```java
/**
 * Разбор узла objectWrapperFactory
 *
 * @param context objectWrapperFactory узел
 */
private void objectWrapperFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
        configuration.setObjectWrapperFactory(factory);
    }
}

Методы реализуются через рефлексию для получения экземпляра `ObjectWrapperFactory`, который затем присваивается свойству `Configuration#objectWrapperFactory`.

### Разбор и конфигурация фабрики рефлексии (узел reflectorFactory)
В предыдущем разделе мы упомянули `ReflectorFactory`, который является вспомогательной фабрикой для создания `Reflector` на основе класса. Он определяет три метода:
```markdown
```java
/**
 * Интерфейс фабрики рефлексии
 */
public interface ReflectorFactory {

    /**
     * Включен ли кеш классов
     */
    boolean isClassCacheEnabled();

    /**
     * Установка кеша классов
     */
    void setClassCacheEnabled(boolean classCacheEnabled);
}

### Получение кэшированной информации для указанного класса

```java
/**
 * Получение кэшированной информации для указанного класса
 */
Reflector findForClass(Class<?> type);
```Он создает объект `Reflector`, который используется для кэширования базовой информации о определенном классе, включая тип класса, имя читаемых и записываемых свойств, а также соответствующие методы `getter` и `setter`, конструкторы и т.д.

`DefaultReflectorFactory` является стандартной реализацией `ReflectorFactory` и предоставляет функциональность кэширования объектов `Reflector`. Кэширование основано на свойстве `reflectorMap` типа `ConcurrentMap<Class<?>, Reflector>`.

Для определения DTO `reflectorFactory` используется структура, аналогичная `objectWrapperFactory`:
```xml
<!ELEMENT reflectorFactory EMPTY>
<!ATTLIST reflectorFactory
type CDATA #REQUIRED
>

reflectorFactory должен иметь атрибут type, указывающий на экземпляр ReflectorFactory, который может использовать псевдоним.

Методы его разбора аналогичны методам разбора objectWrapperFactory:

// Настройка фабрики рефлектора
reflectorFactoryElement(root.evalNode("reflectorFactory"));
/**
 * Разбор узла reflectorFactory
 *
 * @param context узел reflectorFactory
 */
private void reflectorFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
        configuration.setReflectorFactory(factory);
    }
}

Метод реализован через рефлексию для получения экземпляра ReflectorFactory, который затем присваивается свойству Configuration#reflectorFactory.

Инициализация глобальной конфигурации MyBatis через settings

Разговор о том, как инициализировать глобальную конфигурацию MyBatis через settings, приводит нас к ключевому объекту Configuration.Объект Configuration является одним из центральных объектов MyBatis, он определяет множество свойств и методов. В процессе анализа мы можем встретить некоторые свойства и методы, которые не понимаем, но это не важно, так как мы будем постепенно заполнять наши знания о Configuration в процессе дальнейшего анализа.Вот пример свойств объекта Configuration:

/**
 * Класс конфигурации MyBatis
 * Предоставляет все конфигурации MyBatis и контейнер метаданных для маппинговых файлов
 *
 * @author Clinton Begin
 */
public class Configuration {
       * Информация об окружении, включая транзакции и источники данных
       */
      protected Environment environment;
      /**
       * Разрешено ли использование {@link RowBounds} для пагинации в вложенных выражениях
       */
      protected boolean safeRowBoundsEnabled;
      /**
       * Разрешено ли использование {@link ResultHandler} для пагинации в вложенных выражениях
       */
      protected boolean safeResultHandlerEnabled = true;
      /**
       * Преобразование подчеркиваний в camelCase
       */
      protected boolean mapUnderscoreToCamelCase;
      /**
       * Разрешено ли агрессивное ленивое загрузочное состояние
       */
      protected boolean aggressiveLazyLoading;
      /**
       * Разрешено ли возврат нескольких результатов одним SQL-запросом
       */
      protected boolean multipleResultSetsEnabled = true;
      /**
       * Разрешено ли использование JDBC для генерации ключей
       */
      protected boolean useGeneratedKeys;
      /**
       * Использование меток столбцов вместо имен столбцов
       */
      protected boolean useColumnLabel = true;
      /**
       * Включен ли кэш
       */
      protected boolean cacheEnabled = true;
      /**
       * Установка значений для null-полей
       */
      protected boolean callSettersOnNulls;
      /**
       * Использование реальных имен параметров, что может уменьшить количество кода, написанного с использованием {@link org.apache.ibatis.annotations.Param}
       */
      protected boolean useActualParamName = true;
``````markdown
      * Возврат пустого экземпляра при отсутствии данных
      * По умолчанию, если результат запроса пустой, MyBatis возвращает null. Если включить эту опцию, будет возвращен пустой экземпляр.
      */
      protected boolean returnInstanceForEmptyRow;
      /**
       * Префикс лога MyBatis
       */
      protected String logPrefix;
      /**
       * Класс реализации лога, если не указан, будет автоматически определен
       */
      protected Class<? extends Log> logImpl;
      /**
       * Виртуальная файловая система, предоставляющая простой API для доступа к системным файлам
       */
      protected Class<? extends VFS> vfsImpl;
      /**
       * Срок жизни кэша
       * MyBatis использует локальное кэширование для предотвращения циклических ссылок и ускорения повторных вложенных запросов.
       * По умолчанию значение равно SESSION, что означает, что кэш будет использоваться для всех запросов в рамках одного сессионного объекта.
       * Если значение равно STATEMENT, локальное кэширование будет использоваться только для одного SQL-запроса.
       */
      protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
      /**
       * Тип поля в базе данных
       * Указывает JDBC-тип для параметра, если он не указан явно.
       * Некоторые драйверы требуют указания типа столбца JDBC, обычно достаточно использовать общие типы, такие как NULL, VARCHAR или OTHER.
       */
      protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
      /**
       * Триггеры для ленивой загрузки
       */
      protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
      /**
```      * Значение по умолчанию для таймаута {@link java.sql.Statement}, которое определяет количество секунд, в течение которых драйвер будет ожидать ответа от базы данных.
       */
      protected Integer defaultStatementTimeout;
      /**
       * Значение по умолчанию для размера выборки данных
       * Устанавливает значение по умолчанию для количества строк, которые будут загружены за один раз.
       */
      protected Integer defaultFetchSize;
      /**
       * Тип поведения исполнителя, используемого при выполнении ожидаемых операций
       * Включает:
       * {@link ExecutorType#SIMPLE} создает новый подготовленный запрос {@link java.sql.PreparedStatement} для каждого исполненного запроса
       * {@link ExecutorType#REUSE} переиспользует подготовленные запросы
       * {@link ExecutorType#BATCH} переиспользует подготовленные запросы и выполняет операции в пакетном режиме
       */
      protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
      /**
       * Определение поведения автоматической отображаемости
       * {@link AutoMappingBehavior#NONE} отключает автоматическую отображаемость
       * {@link AutoMappingBehavior#PARTIAL} частичная автоматическая отображаемость, отображает только те результаты, для которых не определены вложенные отображаемости
       * {@link AutoMappingBehavior#FULL} полная автоматическая отображаемость, автоматически отображает любые сложные результаты (независимо от того, вложены ли они)
       */
      protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
      /**
      * Поведение при обнаружении неизвестного поля при автоматической отображаемости
      * {@link AutoMappingUnknownColumnBehavior#NONE} не выполняет никаких действий
      * {@link AutoMappingUnknownColumnBehavior#WARNING} выводит предупреждение в лог
      * {@link AutoMappingUnknownColumnBehavior#FAILING} прекращает автоматическую отображаемость и выбрасывает исключение
      */
      protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
      /**
       * Свойства конфигурации configuration->settings
       */
      protected Properties variables = new Properties();
      /**
       * Конфигурация фабрики рефлексии для упрощения работы с свойствами и конструкторами
       */
      protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
      /**
       * Фабрика для создания конфигурационных объектов
       */
      protected ObjectFactory objectFactory = new DefaultObjectFactory();
      /**
       * Фабрика для упаковки конфигурационных объектов,主要用于创建非原生对象
       */
      protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
      /**
       * Включено ли ленивое загрузка. Когда включено, все связанные объекты будут загружаться с отложением. В определенных связях можно перекрыть это состояние с помощью свойства fetchType.
       */
      protected boolean lazyLoadingEnabled = false;
      /**
       * Фабрика прокси, указывает MyBatis на использование определенного инструмента для создания ленивых объектов
       */
      protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Используем встроенный Javassist вместо OGNL
      /**
       * Фабрика прокси, указывает MyBatis на использование определенного инструмента для создания ленивых объектов
       */      * Уникальный идентификатор типа базы данных, Mybatis использует этот идентификатор для выполнения различных запросов для разных производителей баз данных.
       */
      protected String databaseId;
      /**
       * Фабрика конфигурации
       * Configuration factory class.
       * Используется для создания конфигурации для загрузки десериализованных непрочитанных свойств.
       *
       * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
       */
      protected Class<?> configurationFactory;
      /**
       * Регистратор DAO-операций
       */
      protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
      /**
       * Цепочка интерцепторов (Mybatis плагины)
       */
      protected final InterceptorChain interceptorChain = new InterceptorChain();
      /**
       * Регистратор обработчиков типов
       */
      protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
      /**
       * Регистратор псевдонимов типов,主要用于执行SQL语句的输入和输出参数以及一些类的简写
       */
      protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
      /**
       * Регистратор драйверов языка
       */
      protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
      /**
       * Регистратор объявленных SQL-запросов
       */
      protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
              .conflictMessageProducer((savedValue, targetValue) ->
                      ". Пожалуйста, проверьте " + savedValue.getResource() + " и " + targetValue.getResource());
      /**
       * Регистратор кэшированных запросов
       */
      protected final Map<String, Cache> caches = new StrictMap<>("Коллекция кэшей");
      /**
       * Map-карта результатов
       */
     protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Коллекция карт результатов");
      /**
       * Карта параметров
       */
      protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Коллекция карт параметров");
      /**
       * Карта генераторов ключей
       */
      protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Коллекция генераторов ключей");
      /**
       * С集中已加载的资源集合,可以用来防止重复加载文件
       */
      protected final Set<String> loadedResources = new HashSet<>();
      /**
       * Кодовые блоки
       */
      protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");    /**
     * Необработанные объявления
     */
    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<>();

    /*
     * Отношения между кэш-ссылками и кэшами
     * Кэш-ссылка => Кэш
     */
    protected final Map<String, String> cacheRefMap = new HashMap<>();

    public Configuration(Environment environment) {
        this();
        this.environment = environment;
    }    public Configuration() {
         // Регистрация псевдонимов
     }        // Регистрация псевдонима JDBC
         typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
         // Регистрация псевдонима для управления транзакциями
         typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
         // Регистрация псевдонима JNDI
         typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
         // Регистрация псевдонима для пула данных
         typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
         // Регистрация псевдонима для непула данных
         typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
         // Регистрация псевдонима для постоянного кэша
         typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
         // Регистрация псевдонима для кэша FIFO
         typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
         // Регистрация псевдонима для кэша LRU
         typeAliasRegistry.registerAlias("LRU", LruCache.class);
         // Регистрация псевдонима для мягкого кэша
         typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
         // Регистрация псевдонима для слабого кэша
         typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
         // Регистрация псевдонима для поставщика идентификатора базы данных
         typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
         // Регистрация псевдонима для языкового драйвера XML
         typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
         // Регистрация псевдонима для статического языкового драйвера
         typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
         // Регистрация псевдонима для Sl4j логгера
         typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
        // Регистрация псевдонима для Commons логгера
         typeAliasRegistry. registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl. class);
         // Регистрация псевдонима для log4j логгера
         typeAliasRegistry. registerAlias("LOG4J", Log4jImpl. class);
         // Регистрация псевдонима для log4j2 логгера
         typeAliasRegistry. registerAlias("LOG4J2", Log4j2Impl. class);
         // Регистрация псевдонима для JDK логгера
         typeAliasRegistry. registerAlias("JDK_LOGGING", Jdk14LoggingImpl. class);
         // Регистрация псевдонима для стандартного вывода логгера
         typeAliasRegistry. registerAlias("STDOUT_LOGGING", StdOutImpl. class);
         // Регистрация псевдонима для отсутствия логгера
         typeAliasRegistry. registerAlias("NO_LOGGING", NoLoggingImpl. class);
         // Регистрация псевдонима для CGLIB
         typeAliasRegistry. registerAlias("CGLIB", CglibProxyFactory. class);
         // Регистрация псевдонима для JAVASSIST
         typeAliasRegistry. registerAlias("JAVASSIST", JavassistProxyFactory. class);```markdown
 <! -- По умолчанию используется XML-драйвер -->
 languageRegistry. setDefaultDriverClass(XMLLanguageDriver. class);
 <! -- Поддерживается оригинальный язык -->
 languageRegistry. register(RawLanguageDriver. class);
Эммм... Я еще раз все посмотрел и решил, что слишком много свойств, так что пока оставлю это на потом, позже буду изучать по одному.#### Инициализация глобальной конфигурации Mybatis через настройки
Вернемся к инициализации глобальной конфигурации через настройки.

```java
// Инициализация глобальной конфигурации через настройки
settingsElement(settings);
/**
 * Замена системных настроек пользовательскими настройками
 *
 * @param props пользовательские настройки
 * @see http://www.mybatis.org/mybatis-3/ru/configuration.html#settings
 */
private void settingsElement(Properties props) {
    // Определение того, как Mybatis должен автоматически преобразовывать колонки JDBC в поля или свойства. Это соответствует перечислению AutoMappingBehavior, где
    // NONE: отключает автоматическое отображение
    // PARTIAL: частичное автоматическое отображение, только для тех результатов, которые не имеют определенных вложенных отображений
    // FULL: полное автоматическое отображение, автоматически отображает любые сложные результаты (независимо от того, вложены ли они или нет)
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));

    // Определение того, как Mybatis должен действовать, когда автоматическое отображение не может обработать (распознать) колонки или поля. Это соответствует перечислению AutoMappingUnknownColumnBehavior, где
    // NONE: ничего не делает
    // WARNING: выводит предупреждение в лог
    // FAILING: отображение завершается с ошибкой и выбрасывается исключение SqlSessionException
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
}
``````java
        // Когда включен, вызов любого метода загружает все свойства объекта. В противном случае, каждый свойство загружается по мере необходимости (см. lazyLoadTriggerMethods).
        configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));

        // Разрешено ли возврат нескольких результатов из одного выражения (требуется совместимый драйвер).
        configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));

        // Использовать ли метки столбцов вместо имен столбцов.
        configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
```        // Разрешено ли JDBC автоматически генерировать первичные ключи (требуется совместимый драйвер).
        // Если значение установлено в true, то этот параметр заставит использовать автоматическую генерацию первичных ключей, даже если некоторые драйверы не поддерживают это (например, Derby).
        configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));        // Конфигурация по умолчанию для исполнителя. Соответствует enum ExecutorType.
        // SIMPLE - обычный исполнитель;
        // REUSE - исполнитель будет переиспользовать подготовленные выражения (prepared statements);
        // BATCH - исполнитель будет переиспользовать выражения и выполнять операции в пакетном режиме.
        configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));

        // Установка времени ожидания по умолчанию. Определяет, сколько секунд драйвер будет ожидать ответа от базы данных.
        configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));

        // Установка значения подсказки для размера выборки (fetchSize) драйвера. Этот параметр может быть перекрыт только в настройках запроса.
        configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));

        // Включено ли автоматическое преобразование нижнего подчеркивания в стиль camelCase (camel case). Это означает, что из классического имени столбца A_COLUMN будет создано свойство aColumn в стиле camelCase.
        configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));        // Разрешено ли использование RowBounds для пагинации в вложенных выражениях. Если разрешено, то значение должно быть false.
        configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));        // MyBatis использует локальную кэшированную систему (Local Cache), чтобы предотвратить циклические ссылки (circular references) и ускорить повторные вложенные запросы.
        // По умолчанию значение установлено на SESSION, в этом случае кэширование будет применяться ко всем запросам, выполненным в рамках одного сеанса.
        // Если значение установлено на STATEMENT, локальная кэшированная система будет использоваться только для выполнения одного запроса, и данные не будут поделены между различными вызовами для одного и того же SqlSession.
        configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));        // В случае, когда JDBC-тип не указан для параметра, используется по умолчанию тип NULL, VARCHAR или OTHER, если параметр равен null.
        configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));        // Методы, которые могут быть использованы для триггерирования ленивой загрузки.
        configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals, clone, hashCode, toString"));

        // Управление безопасностью обработчика результата. Если значение установлено в false, то использование обработчика результата не безопасно.
        configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));

        // Указание по умолчанию языка для динамического SQL.
        configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));

        // Указание по умолчанию TypeHandler для Enum.
        configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));

        // Управление вызовом методов setter для null значений в результатах.
        configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));

        // Управление использованием реальных имен параметров в сигнатуре метода.
        configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));

        // Управление возвратом пустого экземпляра вместо null, если все столбцы в строке пустые.
        configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));        // Указание префикса, добавляемого MyBatis к имени лога.
        configuration.setLogPrefix(props.getProperty("logPrefix"));```markdown
Укажите класс, который предоставляет экземпляр Configuration.
Этот возвращаемый экземпляр Configuration используется для загрузки ленивых свойств объектов, которые будут десериализованы.
Этот класс должен содержать статический метод getConfiguration() с сигнатурой static Configuration getConfiguration().
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
Необходимо отметить, что код внутри метода `settingsElement(Properties)` также довольно объемный. Давайте посмотрим на него по частям.
```##### Конфигурация автоматического отображения (настройка автоматического отображения в MyBatis)```java
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));

AutoMappingBehavior — это стратегия, определяющая автоматическое отображение столбцов и полей в Mybatis. Он имеет три значения: NONE, PARTIAL, FULL.

  • NONE — отключает автоматическое отображение.
  • PARTIAL — частичное автоматическое отображение, что означает, что автоматическое отображение будет применяться только к результатам запроса, которые не имеют определенных вложенных результатов.
  • FULL — полное автоматическое отображение, которое автоматически отображает любые сложные результаты запроса, включая вложенные результаты.

Когда мы определяем атрибуты collection или association в результатах запроса ResultMap, операция запроса не является простым SQL-запросом. Вместо этого результат представляет собой сложное множество, состоящее из одного и многих элементов. В этом случае это вложенный результат.

Например, мы определяем следующий маппер:

    <!-- ClassInfo и Student имеют два атрибута: ID и name, но здесь мы не явно указываем их, используя автоматическое отображение Mybatis. -->
    <!-- Определяем результат запроса для класса Student. -->
    <resultMap id="Student" type="*.*.*.Student" >
    </resultMap>
    <!--
        Определяем результат запроса для класса ClassInfo.
        Отношение между классом и студентом: один ко многим.
    -->
    <resultMap id="ClassInfo" type="*.*.*.ClassInfo">
        <collection property="student" resultMap="Student"/>
    </resultMap>
```В этом случае результат запроса для ClassInfo включает коллекцию Student, где эта коллекция Student является вложенным результатом.Таким образом, только когда значение `Configuration#autoMappingBehavior` равно `AutoMappingBehavior#FULL`, автоматически будут присвоены значения ID и Name для объекта Student.

##### Конфигурация #autoMappingUnknownColumnBehavior (Настройка поведения Mybatis при автоматической маппинге столбцов, которые не могут быть идентифицированы)
```java
   configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));

AutoMappingUnknownColumnBehavior — это перечисление, определенное в Mybatis, которое описывает поведение при обнаружении столбцов или полей, которые не могут быть обработаны (идентифицированы) при автоматической маппинге. В этом перечислении определен метод doAction, который реализует конкретные действия.

     /**
      * Выполняет действие при обнаружении неизвестного столбца (или неизвестного типа свойства) в автоматической маппинге.
      *
      * @param mappedStatement объявление отображения
      * @param columnName      имя столбца
      * @param propertyName    имя свойства
      * @param propertyType    тип свойства, если это свойство не null, то это указывает на отсутствие зарегистрированного TypeHandler.
      */
     public abstract void doAction(MappedStatement mappedStatement, String columnName, String propertyName, Class<?> propertyType);

AutoMappingUnknownColumnBehavior имеет три режима, из которых по умолчанию используется NONE.

  • NONE, не выполняет никаких действий
  • WARNING, выводит предупреждающий лог
  • FAILING, отображение завершается ошибкой и выбрасывает {@link SqlSessionException}

Configuration#cacheEnabled(включить кэширование)


cacheEnabled — это переключатель MyBatis для включения всех кэшей, по умолчанию установленный в true.


##### Конфигурация#proxyFactory(Модуль Mybatis для ленивой загрузки объектов)

configuration. setProxyFactory((ProxyFactory) createInstance(props. getProperty("proxyFactory")));

proxyFactory указывает на фабрику прокси-объектов, используемую Mybatis для создания объектов с поддержкой ленивой загрузки. По умолчанию используется JavassistProxyFactory.
Конфигурация#lazyLoadingEnabled(Включение ленивой загрузки)
configuration. setLazyLoadingEnabled(booleanValueOf(props. getProperty("lazyLoadingEnabled"), false));

lazyLoadingEnabled является глобальным переключателем для включения ленивой загрузки в Mybatis. При включении все связанные объекты будут загружаться лениво. Для конкретных связей можно переключить это состояние, установив свойство fetchType.


##### Конфигурация#aggressiveLazyLoading(Загрузка всех свойств объекта при вызове метода)

configuration. setAggressiveLazyLoading(booleanValueOf(props. getProperty("aggressiveLazyLoading"), false));

При включении любой вызов метода будет загружать все свойства объекта. В противном случае каждое свойство будет загружаться по мере необходимости. По умолчанию значение `false` (см. lazyLoadTriggerMethods).
Конфигурация#multipleResultSetsEnabled(Возможность возврата нескольких результатов одним запросом)
configuration. setMultipleResultSetsEnabled(booleanValueOf(props. getProperty("multipleResultSetsEnabled"), true));

По умолчанию значение true.


configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));

По умолчанию значение `true`.

##### Конфигурация#useGeneratedKeys(Возможность использования JDBC для генерации ключей)

configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));

Этот параметр используется для включения автоматической генерации ключей. По умолчанию значение `false`. Если параметр установлен в `true`, то ключи будут автоматически генерироваться, даже если некоторые драйверы не поддерживают это поведение.

##### Конфигурация#defaultExecutorType(Настройка по умолчанию для исполнителя SQL в Mybatis)

configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));

defaultExecutorType соответствует перечислению ExecutorType, которое определяет три типа исполнителей SQL в Mybatis:
- SIMPLE - обычный исполнитель, который создает новый подготовленный запрос для каждого выполнения;
- REUSE - исполнитель, который переиспользует подготовленные запросы;
- BATCH - исполнитель, который переиспользует запросы и выполняет их в виде пакета.
По умолчанию используется `SIMPLE`.

##### Конфигурация#defaultStatementTimeout(Установка времени ожидания запроса Mybatis)

configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));

Это значение определяет количество секунд, в течение которых Mybatis будет ожидать ответ от базы данных, по умолчанию — бесконечное ожидание.##### Конфигурация#defaultFetchSize(установка ограничения на количество строк в результирующем наборе)

configuration. setDefaultFetchSize(integerValueOf(props. getProperty("defaultFetchSize"), null));

Устанавливает предпочтительное значение для количества строк, которые драйвер должен получить из результирующего набора (fetchSize). Этот параметр может быть переопределен только в настройках запроса.
##### Конфигурация#mapUnderscoreToCamelCase(включение автоматического преобразования нижних подчеркиваний в стиль CamelCase)

configuration. setMapUnderscoreToCamelCase(booleanValueOf(props. getProperty("mapUnderscoreToCamelCase"), false));

При включении этого параметра Mybatis автоматически преобразует имена классов JDBC из стиля с нижними подчеркиваниями в стиль CamelCase для свойств Java. Значение по умолчанию — `false`.
##### Конфигурация#safeRowBoundsEnabled(разрешение использования RowBounds в вложенных запросах)

configuration. setSafeRowBoundsEnabled(booleanValueOf(props. getProperty("safeRowBoundsEnabled"), false));

Указывает, разрешено ли использование RowBounds в вложенных результирующих наборах. Значение по умолчанию — `false`, где `false` означает разрешение.
> RowBounds — это класс пагинации Mybatis, подробное использование будет описано позже.##### Конфигурация#localCacheScope(установка жизненного цикла локального кеша Mybatis)

configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));

`LocalCacheScope` — это перечисление, определяющее жизненный цикл локального кеша Mybatis. Оно имеет два значения, соответствующие различным жизненным циклам локального кеша:
- SESSION, в этом случае кеш будет хранить все запросы, выполненные в рамках одного сеанса.
- STATEMENT, локальный кеш будет использоваться только для выполнения запросов, и данные не будут разделяться между различными вызовами одного и того же `SqlSession`.

Значение по умолчанию — `SESSION`.

##### Конфигурация#jdbcTypeForNull(установка значения JDBC типа по умолчанию для параметров, равных NULL)

configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));

Это значение определяет тип JDBC, используемый по умолчанию для параметров, равных NULL, когда тип JDBC для параметра не указан. Обычно это один из типов NULL, VARCHAR или OTHER. Значение по умолчанию — `OTHER`.

##### Конфигурация#lazyLoadTriggerMethods(установка методов, вызывающих ленивую загрузку в Mybatis)

configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));

Этот параметр упоминается в `aggressiveLazyLoading`, и оба параметра обычно используются вместе. Он определяет методы, вызывающие ленивую загрузку, и указывает, какие методы объектов вызывают ленивую загрузку. По умолчанию значение равно: `equals,clone,hashCode,toString`.##### Конфигурация#safeResultHandlerEnabled(разрешение использования ResultHandler для пагинации в вложенных выражениях)

configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));

Устанавливает, разрешено ли MyBatis использование ResultHandler для пагинации в вложенных выражениях. Внимание!! Если разрешено, устанавливайте значение в `false`. По умолчанию значение равно `false`.

##### Конфигурация#languageRegistry(регистратор языковых драйверов для MyBatis скриптов)

configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));


/**
 * Устанавливает по умолчанию язык скриптов для MyBatis
 *
 * @param driver язык скриптов
 */
public void setDefaultScriptingLanguage(Class<? extends LanguageDriver> driver) {
    if (driver == null) {
        driver = XMLLanguageDriver.class;
    }
    // Регистрирует язык скриптов по умолчанию в регистраторе языковых драйверов
    getLanguageRegistry().setDefaultDriverClass(driver);
}

public LanguageDriverRegistry getLanguageRegistry() { return languageRegistry; }


/** * Устанавливает тип по умолчанию для языка скриптов * * @param defaultDriverClass тип по умолчанию для языка скриптов */ public void setDefaultDriverClass(Class<? extends LanguageDriver> defaultDriverClass) { // Регистрирует тип по умолчанию для языка скриптов register(defaultDriverClass); this.defaultDriverClass = defaultDriverClass; }

Устанавливает по умолчанию язык скриптов для MyBatis. По умолчанию используется `XMLLanguageDriver` SQL-драйвер.В интерфейсе `LanguageDriver` определены три метода:
```markdown
## Создание обработчика параметров

```java
/**
 * Создает {@link ParameterHandler}, который передает фактические параметры JDBC-запросу.
 *
 * @param mappedStatement Заявление, которое выполняется
 * @param parameterObject Объект входных параметров
 * @param boundSql        Сгенерированное SQL
 * @return Обработчик параметров
 * @author Frank D. Martinez [mnesarco]
 * @see DefaultParameterHandler
 */
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);

/**
 * Создает {@link SqlSource}, который сохраняет SQL-запросы, прочитанные из XML-файла маппера.
 * SQL-запросы из XML-файла читаются при запуске.
 *
 * @param configuration Конфигурация Mybatis
 * @param script        XNode, прочитанный из XML-файла
 * @param parameterType Тип входных параметров
 * @return Источник SQL
 */
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);

/**
 * Создает {@link SqlSource}, который сохраняет SQL-запросы, прочитанные из XML-файла маппера.
 * SQL-запросы из класса читаются при запуске.
 *
 * @param configuration Конфигурация Mybatis
 * @param script        Содержимое аннотации
 * @param parameterType Тип входных параметров
 * @return Источник SQL
 */
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);

Дополнительные сведения

```Кроме драйвера XMLLanguageDriver в Mybatis, типичным примером использования является драйвер `MybatisXMLLanguageDriver` в `Mybatis-plus`, который переопределяет метод `createParameterHandler` драйвера `XMLLanguageDriver`, чтобы использовать пользовательский `MybatisDefaultParameterHandler`.`LanguageDriverRegistry` — это регистр обработчиков SQL-запросов Mybatis. Он поддерживает коллекцию всех типов драйверов, которые могут обрабатывать SQL-запросы в Mybatis. Внутри него хранится коллекция `LANGUAGE_DRIVER_MAP`, которая содержит типы обработчиков SQL-запросов и их экземпляры.

/**
 * Регистр обработчиков SQL-запросов Mybatis
 *
 * @author Frank D. Martinez [mnesarco]
 */
public class LanguageDriverRegistry {

    /**
     * Регистр обработчиков SQL-запросов
     * Тип обработчика SQL-запросов => Экземпляр обработчика SQL-запросов
     */
    private final Map<Class<? extends LanguageDriver>, LanguageDriver> LANGUAGE_DRIVER_MAP = new HashMap<>();

    /**
     * По умолчанию используется обработчик SQL-запросов
     */
    private Class<? extends LanguageDriver> defaultDriverClass;
}
/**
 * Регистрация нового обработчика языка Mybatis
 *
 * @param cls тип обработчика языка
 */
public void register(Class<? extends LanguageDriver> cls) {
    if (cls == null) {
        throw new IllegalArgumentException("null is not a valid Language Driver");
    }
    if (!LANGUAGE_DRIVER_MAP.containsKey(cls)) {
        try {
            // Регистрация
            LANGUAGE_DRIVER_MAP.put(cls, cls.newInstance());
        } catch (Exception ex) {
            throw new ScriptingException("Failed to load language driver for " + cls.getName(), ex);
        }
    }
}

/**
 * Регистрация нового обработчика языка Mybatis
 *
 * @param instance обработчик языка
 */
public void register(LanguageDriver instance) {
    if (instance == null) {
        throw new IllegalArgumentException("null is not a valid Language Driver");
    }
    // Получение типа Class
    Class<? extends LanguageDriver> cls = instance.getClass();
    if (!LANGUAGE_DRIVER_MAP.containsKey(cls)) {
        // Регистрация
        LANGUAGE_DRIVER_MAP.put(cls, instance);
    }
}```

Метод configuration#setDefaultScriptingLanguage(Class) вызывает метод setDefaultDriverClass из класса LanguageDriverRegistry для установки глобального по умолчанию обработчика языка SQL. Из configuration#setDefaultScriptingLanguage(Class) также можно увидеть, что по умолчанию Mybatis использует XMLLanguageDriver в качестве обработчика языка SQL.


configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")))


/** * Устанавливает по умолчанию TypeHandler для обработки типов перечислений. По умолчанию используется EnumTypeHandler. * * @param typeHandler TypeHandler для обработки типов перечислений * @since 3.4.5 */ public void setDefaultEnumTypeHandler(Class<? extends TypeHandler> typeHandler) { if (typeHandler != null) { // Получаем TypeHandlerRegistry и устанавливаем по умолчанию TypeHandler для обработки типов перечислений getTypeHandlerRegistry().setDefaultEnumTypeHandler(typeHandler); } }

Получаем экземпляр TypeHandlerRegistry из Configuration.
/**
 * Получает TypeHandlerRegistry
 */
public TypeHandlerRegistry getTypeHandlerRegistry() {
    return typeHandlerRegistry;
}
Устанавливаем по умолчанию TypeHandler для обработки типов перечислений.

/** * Устанавливает по умолчанию TypeHandler для обработки типов перечислений. По умолчанию используется EnumTypeHandler. * * @param typeHandler TypeHandler для обработки типов перечислений * @since 3.4.5 */ public void setDefaultEnumTypeHandler(Class<? extends TypeHandler> typeHandler) { this.defaultEnumTypeHandler = typeHandler; }

`TypeHandlerRegistry` является регистром TypeHandler для Mybatis, который отвечает за поддержание TypeHandler для преобразования типов Java и JDBC.`TypeHandlerRegistry` использует свойство `defaultEnumTypeHandler` для записи по умолчанию TypeHandler для перечислений.

Параметр `defaultEnumTypeHandler` используется для установки значения по умолчанию для `TypeHandlerRegistry#defaultEnumTypeHandler`, по умолчанию это `EnumTypeHandler`.

##### Configuration#callSettersOnNulls(указывает, следует ли вызывать методы setter (или put для map) для свойств объекта, значение которых равно null в результирующем наборе)

configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));

Устанавливает, следует ли вызывать методы setter для свойств объекта, значение которых равно null в результирующем наборе. Значение по умолчанию равно `false`.

##### Конфигурация#returnInstanceForEmptyRow(При пустом запросе, создавать ли пустой экземпляр)

configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));

Устанавливает, создавать ли пустой экземпляр при пустом результирующем наборе MyBatis. Значение по умолчанию — `false`, то есть создание не производится.

> Важно отметить, что при включении этой функции, она также применяется к вложенным результатам (collection и association).

##### Конфигурация#logPrefix(Устанавливает общую префиксную метку для логов MyBatis)

configuration.setLogPrefix(props.getProperty("logPrefix"));

Устанавливает общую префиксную метку для всех логов MyBatis.##### Конфигурация#configurationFactory(Указывает фабрику конфигурации)

configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));

Указывает класс, который предоставляет экземпляр `Configuration`. Этот экземпляр `Configuration` используется для загрузки лениво-загружаемых свойств объектов, которые были десериализованы.> Класс, указанный в `configurationFactory`, должен иметь статический метод `static Configuration getConfiguration()`.

Хорошо, процесс конфигурации `Configuration` на основе элемента `settings` завершен. Этот раздел довольно сухой и сложный, поэтому достаточно иметь общее представление о различных полях.

### Разбор элемента environments, конфигурация нескольких окружений MyBatis
После завершения сухого процесса конфигурации `Configuration` на основе элемента `settings`, переходим к разбору элемента `environments`, чтобы настроить несколько окружений в MyBatis.

MyBatis по умолчанию поддерживает конфигурацию нескольких окружений. В MyBatis существует объект `Environment`, который имеет три простых параметра:

/**

  • Контейнер окружения MyBatis
  • @author Clinton Begin */ public final class Environment { // Уникальный идентификатор окружения private final String id; // Фабрика транзакций private final TransactionFactory transactionFactory; // Источник данных private final DataSource dataSource; }
В этом контексте `id` является уникальным идентификатором текущего окружения, что является семантическим свойством.

`transactionFactory` представляет собой объект `TransactionFactory`, который является фабрикой для создания объектов `Transaction`. Объект `Transaction` упаковывает `Connection` JDBC и управляет жизненным циклом соединения с базой данных, включая его создание, подтверждение/откат и закрытие. `dataSource` свойство соответствует объекту `DataSource`, который указывает на JDBC источник данных.DTD определение для `environments` в MyBatis выглядит следующим образом:
В `environments` необходимо указать значение атрибута `default`, которое является идентификатором по умолчанию для среды и используется для указания используемой по умолчанию среды. В `environments` также допускается наличие одного или более `environment` дочерних элементов.
Элемент `environment` имеет обязательный атрибут `id`, который является уникальным маркером конфигурации текущей среды, а также содержит обязательные элементы `transactionManager` и `dataSource`.

#### transactionManager
Элемент `transactionManager` используется для конфигурации текущего менеджера транзакций, его DTD определение выглядит следующим образом:
Элемент `transactionManager` имеет обязательный атрибут `type`, который указывает тип используемого менеджера транзакций. В MyBatis по умолчанию предоставляются два типа менеджеров транзакций: `JDBC` и `MANAGED`.

Эти два кратких имени опираются на механизм псевдонимов типов в MyBatis, они регистрируются в конструкторе без параметров класса `Configuration`:
public Configuration() {
    // Регистрация псевдонимов
}
        typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        // Регистрация псевдонима менеджера транзакций
        typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
        ...
        }

Где JDBC соответствует классу JdbcTransactionFactory, который создает объект JdbcTransaction, использующий стандартные настройки подтверждения и отмены транзакций JDBC, зависящие от соединения, полученное из источника данных.А MANAGED соответствует классу ManagedTransactionFactory, который создает объект ManagedTransaction, являющийся одним из реализаций интерфейса Transaction, который игнорирует все запросы подтверждения и отмены транзакций, а также по умолчанию имеет возможность закрытия соединения, хотя это можно настроить так, чтобы эта возможность была отключена.

Также в элементе transactionManager можно настроить несколько элементов property, используемых для параметров пользовательского менеджера транзакций.

dataSource

Элемент dataSource используется для конфигурации JDBC источника данных, его DTD определение выглядит следующим образом:

<!ELEMENT dataSource (property*)>
<!ATTLIST dataSource
type CDATA #REQUIRED
>

dataSource тег также имеет обязательное свойство type, которое используется для указания конкретного экземпляра JDBC data source. В MyBatis по умолчанию предоставляются три типа источников данных: UNPOOLED, POOLED и JNDI.Аналогично, эти три кратких имени также являются псевдонимами типов Mybatis, которые регистрируются в конструкторе без параметров объекта Configuration:

public Configuration() {
        // регистрация псевдонимов
        
        ...
        
        // регистрация псевдонима JNDI
        typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
        // регистрация псевдонима пула данных
        typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
        // регистрация псевдонима непула данных
        typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
        
        ...
        
        }

В частности, JNDI соответствует JndiDataSourceFactory, который используется для загрузки доступного DataSource с помощью JNDI.POOLED соответствует PooledDataSourceFactory, который используется для получения PooledDataSource. PooledDataSource представляет собой простой, синхронизированный, потокобезопасный пул подключений к базе данных, который использует повторное использование объектов JDBC Connection, чтобы избежать времени инициализации и аутентификации при создании новых подключений, что повышает способность приложения к параллельному доступу к базе данных.

UNPOOLED соответствует UnpooledDataSourceFactory, который используется для получения UnpooledDataSource. UnpooledDataSource представляет собой простой источник данных, который для каждого запроса на получение подключения открывает новое подключение.Под тегом dataSource допускается наличие нескольких тегов property, которые будут преобразованы в Properties для инициализации и конфигурации соответствующего DataSource. Понимая функции каждого элемента, мы продолжаем анализ кода для разбора environments. Вызов парсера (XmlConfigBuilder):

  // Загрузка конфигураций для нескольких окружений, поиск текущего окружения (по умолчанию - default) соответствующего менеджера транзакций и источника данных
  environmentsElement(root.evalNode("environments"));

Парсинг и построение окружения MyBatis.

    /**
     * Парсинг узла environments
     *
     * @param context узел environments
     */
    private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
            if (environment == null) {
                // Конфигурация по умолчанию
                environment = context.getStringAttribute("default");
            }
            for (XNode child : context.getChildren()) {
                // Получение уникального идентификатора окружения
                String id = child.getStringAttribute("id");
                if (isSpecifiedEnvironment(id)) {
                    // Конфигурация по умолчанию источника данных
                    // Создание фабрики транзакций
                    TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                    // Создание фабрики источника данных
                    DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                    // Создание источника данных
                    DataSource dataSource = dsFactory.getDataSource();

Чтобы создать контейнер среды с уникальным идентификатором среды, фабрикой транзакций и источником данных, используется следующий код:```java Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource);

configuration.setEnvironment(environmentBuilder.build());


Процесс анализа тега `environments` не является сложным. Он начинается с получения уникального идентификатора указанной пользователем среды по свойству `default` тега `environments`.

Затем происходит перебор всех тегов `environment` внутри тега `environments`. Уникальный идентификатор конфигурируемой среды получается по свойству `id` тега `environment`.

Если текущий идентификатор совпадает с указанным пользователем идентификатором по умолчанию, то обрабатывается этот тег `environment`. В противном случае обработка пропускается.

```java
private boolean isSpecifiedEnvironment(String id) {
    if (environment == null) {
        throw new BuilderException("No environment specified.");
    } else if (id == null) {
        throw new BuilderException("Environment requires an id attribute.");
    } else if (environment.equals(id)) {
        return true;
    }
    return false;
}
```При обработке тега `environment` используются конфигурации элементов `transactionManager` и `dataSource` для получения объектов `TransactionFactory` и `DataSourceFactory`. Затем с помощью `DataSourceFactory` получается экземпляр объекта `DataSource`. В конце используется конструктор объекта `Environment`, чтобы создать объект `Environment`, который затем синхронизируется с конфигурационным классом MyBatis `Configuration`.

```Процесс разбора `transactionManager`:
 // Создание фабрики транзакций
 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));

 /**
  * Настройка механизма управления транзакциями на основе элемента environments>environment>transactionManager
  *
  * @param context содержимое элемента environments>environment>transactionManager
  */
 private TransactionFactory transactionManagerElement(XNode context) throws Exception {
     if (context != null) {
         // Получение типа транзакции
         String type = context.getStringAttribute("type");
         // Получение конфигурации параметров транзакции
         Properties props = context.getChildrenAsProperties();
         // Создание фабрики транзакций
         TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
         // Установка пользовательских параметров
         factory.setProperties(props);
         return factory;
     }
     throw new BuilderException("Настройка окружения требует фабрики транзакций.");
 }
Процесс разбора `transactionManager` довольно прост. Сначала получается атрибут `type` элемента `transactionManager`, который представляет собой псевдоним управляющего транзакциями.Затем с помощью метода `BaseBuilder#resolveClass()` (этот метод был рассмотрен ранее) из таблицы псевдонимов MyBatis находит конкретный тип фабрики транзакций. С помощью рефлексии создается экземпляр `TransactionFactory`, а затем методом `TransactionFactory#setProperties(Properties)` устанавливаются пользовательские параметры. Таким образом, элемент `transactionManager` успешно разобран. Процесс парсинга элемента `dataSource`:

```java
// Создаем фабрику данных
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
/**
 * Парсинг элемента dataSource
 */
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
        // Получаем алиас DataSourceFactory, указанный атрибутом type элемента dataSource.
        String type = context.getStringAttribute("type");
        // Получаем коллекцию параметров конфигурации, определенных пользователем.
        Properties props = context.getChildrenAsProperties();
        // Парсим алиас и получаем рефлексию для конкретного типа DataSourceFactory.
        DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
        // Устанавливаем параметры
        factory.setProperties(props);
        return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

Процесс парсинга элемента dataSource очень похож на процесс парсинга элемента transactionManager.Сначала по атрибуту type получаем псевдоним для DataSourceFactory, затем получаем коллекцию параметров конфигурации, определённых пользователем, далее парсим псевдоним и получаем рефлексию для конкретного типа DataSourceFactory, и в конце устанавливаем параметры конфигурации в DataSourceFactory.После завершения парсинга и инициализации transactionManager и dataSource, MyBatis использует DataSourceFactory для получения конкретного экземпляра DataSource. В этот момент все необходимые элементы для конфигурации объекта Environment уже получены. Затем с помощью конструктора объекта Environment создается экземпляр Environment, который синхронизируется с атрибутом environment класса конфигурации MyBatis Configuration.

Таким образом, парсинг элемента environments завершен.

Парсинг элемента databaseIdProvider, конфигурация генератора уникального идентификатора базы данных

MyBatis предоставляет возможность выполнения различных SQL-запросов в зависимости от типа используемой базы данных. Это достигается за счет использования атрибута databaseId в SQL-запросах. После определения типа текущей базы данных, MyBatis загружает все SQL-запросы, которые не имеют атрибута databaseId, а также те, которые соответствуют текущему типу базы данных. Если SQL-запросы имеют как атрибут databaseId, так и не имеют его, то будут использованы запросы с атрибутом databaseId. Что касается определения текущего типа базы данных и преобразования его в databaseId, эта функция реализуется интерфейсом DatabaseIdProvider, который определяет метод getDatabaseId(DataSource). Этот метод позволяет получить databaseId для указанного источника данных.Элемент databaseIdProvider используется для указания конкретной реализации класса DatabaseIdProvider, используемой Mybatis.

Его DTD определен следующим образом:

<!ELEMENT databaseIdProvider (property*)>
<!ATTLIST databaseIdProvider
type CDATA #REQUIRED
>

Элемент databaseIdProvider имеет обязательный атрибут type, который указывает реализацию класса DatabaseIdProvider, используемую Mybatis для генерации databaseId. Этот параметр может использовать псевдонимы Mybatis.

В настоящее время Mybatis регистрирует только один псевдоним DB_VENDOR в конфигурации, который указывает на экземпляр класса VendorDatabaseIdProvider.

  public Configuration() {
  
      ...
      // Регистрация поставщика идентификатора базы данных
        typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
      ...
  }

VendorDatabaseIdProvider использует DatabaseMetaData#getDatabaseProductName для определения databaseId.

Когда пользователь использует VendorDatabaseIdProvider как поставщика уникального идентификатора базы данных, ему необходимо предоставить соответствующие отношения между типом базы данных и databaseId.Тип базы данных используется для проверки, содержит ли имя продукта метаданные указанного источника данных значение, и возвращает databaseId в соответствии с определением пользователя. Пользователь определяет связь между типом базы данных и databaseId с помощью элемента databaseIdProvider и его подэлемента property.

Например:

   <databaseIdProvider type="DB_VENDOR">
     <property name="Apache Derby" value="derby"/>
     <property name="SQL Server" value="sqlserver"/>
     <property name="DB2" value="db2"/>
     <property name="Oracle" value="oracle"/>
   </databaseIdProvider>

Далее переходим к коду для парсинга элемента databaseIdProvider:

     /**
      * Парсинг элемента databaseIdProvider
      *
      * @param context элемент databaseIdProvider
      */
     private void databaseIdProviderElement(XNode context) throws Exception {
         DatabaseIdProvider databaseIdProvider = null;
         if (context != null) {
             String type = context.getStringAttribute("type");
             // ужасный патч для обратной совместимости
             if ("VENDOR".equals(type)) {
                 type = "DB_VENDOR";
             }
             // получаем конфигурацию пользовательских типов базы данных и databaseId
             Properties properties = context.getChildrenAsProperties();
             // получаем экземпляр DatabaseIdProvider
             databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
             // настраиваем соответствие типов базы данных и databaseId
             databaseIdProvider.setProperties(properties);
         }
         // получаем контейнер Environment
         Environment environment = configuration.getEnvironment();
         if (environment != null && databaseIdProvider != null) {
             // получаем текущий databaseId
             String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
         }
     }
```            // синхронизируем значение configuration#databaseId
             configuration.setDatabaseId(databaseId);
         }
     }

Парсинг элемента databaseIdProvider довольно прост. Сначала получаем имя экземпляра DatabaseIdProvider, указанное атрибутом type элемента databaseIdProvider. Затем обеспечиваем совместимость псевдонима VENDOR с DB_VENDOR. Затем используем метод BaseBuilder#resolveClass(String) для получения типа экземпляра DatabaseIdProvider, а затем используем рефлексию для получения соответствующего экземпляра и инициализации соответствия типов базы данных и databaseId. После получения объекта Environment (из Configuration#environment) для текущей среды MyBatis, используем метод getDatabaseId(DataSource) из полученного экземпляра DatabaseIdProvider, чтобы получить значение databaseId для текущей среды, а затем присваиваем это значение Configuration#databaseId.На этом парсинг элемента databaseIdProvider завершен.

Парсинг элемента typeHandlers Mybatis, конфигурация типовых обработчиков преобразования типов

В предыдущих статьях мы упоминали, что TypeHandlerRegistry поддерживает регистр типовых обработчиков преобразования типов Mybatis.

Типовые обработчики преобразования типов (TypeHandler) представляют собой интерфейс, определяющий стратегию преобразования типов Java и JDBC. Они должны предоставлять следующие четыре метода:

  • Установка значения в PreparedStatement по индексу параметра и типу JDBC с помощью метода setParameter(PreparedStatement, int, T, JdbcType).
  • Получение значения из столбца ResultSet по имени и преобразование его в соответствующий тип Java с помощью метода getResult(ResultSet, String).
  • Получение значения из столбца ResultSet по индексу и преобразование его в соответствующий тип Java с помощью метода getResult(ResultSet, int).
  • Получение значения из параметра хранимой процедуры CallableStatement по индексу и преобразование его в соответствующий тип Java с помощью метода getResult(CallableStatement, int).

В TypeHandlerRegistry по умолчанию регистрируются некоторые часто используемые типовые обработчики преобразования типов. Конкретные коды регистрации можно найти в конструкторе без параметров TypeHandlerRegistry.

В течение всего жизненного цикла Mybatis, конструктор без параметров TypeHandlerRegistry вызывается в методе XMLConfigBuilder#XMLConfigBuilder(XPathParser, String, Properties) через super(new Configuration())Configuration есть переменная typeAliasRegistry, отмеченная как final, которая инициализируется в процессе инициализации Configuration через вызов конструктора без параметров typeAliasRegistry.

    /**
     * Регистр типовых псевдонимов, используется для упрощения входных и выходных параметров SQL-запросов и некоторых классов
     */
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

Хотя Mybatis по умолчанию регистрирует множество часто используемых TypeHandler, при работе с сложными пользовательскими объектами часто требуется предоставить свои собственные типовые обработчики преобразования типов, например, для пользовательских обработчиков преобразования типов перечислений. В этом случае нам нужно зарегистрировать наш обработчик преобразования типов в Mybatis, и мы можем использовать typeHandlers для конфигурации.

В DTD Mybatis для typeHandlers определено следующим образом:

<!ELEMENT typeHandlers (typeHandler*, package*)>

Под элементом typeHandlers могут располагаться ноль или более элементов typeHandler или package.

Каждый элемент typeHandler имеет три атрибута: javaType, jdbcType и handler. Атрибуты javaType и jdbcType являются необязательными и представляют собой типы Java и JDBC соответственно. Атрибут handler является обязательным и указывает на тип обработчика преобразования типов. Каждый элемент typeHandler соответствует конфигурации одного экземпляра TypeHandler.Эти три параметра могут использовать механизм псевдонимов MyBatis, так как их разрешение происходит с помощью метода BaseBuilder#resolveClass(String).Элемент package имеет один обязательный атрибут name, который представляет собой базовый пакет, который пользователь хочет зарегистрировать. MyBatis будет рекурсивно обрабатывать все доступные экземпляры TypeHandler в базовом пакете и его подпакетах. Анализ позиции входа для typeHandlers находится в методе XmlConfigBuilder#parseConfiguration(XNode):

   // Регистрация обработчиков типов
   typeHandlerElement(root.evalNode("typeHandlers"));

Этот метод делегирует задачу парсинга и регистрации typeHandlers методу typeHandlerElement(XNode):

  // Парсинг элемента typeHandlers
    private void typeHandlerElement(XNode parent) {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                if ("package".equals(child.getName())) {
                    // Регистрация всего пакета
                    String typeHandlerPackage = child.getStringAttribute("name");
                    typeHandlerRegistry.register(typeHandlerPackage);
                } else {
                    // Регистрация отдельных элементов
                    // Получение имени Java-типа
                    String javaTypeName = child.getStringAttribute("javaType");
                    // Получение имени JDBC-типа
                    String jdbcTypeName = child.getStringAttribute("jdbcType");
                    // Получение имени типа обработчика
                    String handlerTypeName = child.getStringAttribute("handler");
                    // Парсинг Java-типа
                    Class<?> javaTypeClass = resolveClass(javaTypeName);
                    // Парсинг JDBC-типа
                    JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
                    // Парсинг типа обработчика
                    Class<?> typeHandlerClass = resolveClass(handlerTypeName);

Регистрация обработчиков типов#### Регистрация обработчиков типов

Для регистрации обработчиков типов Mybatis разделяет обработку на две категории: парсинг элемента package и парсинг элемента typeHandler.

Парсинг элемента package

Элемент package имеет один обязательный атрибут name, который указывает базовый пакет, содержащий обработчики типов. Mybatis передает это значение в метод TypeHandlerRegistry.register(String) для выполнения регистрации всех классов в этом пакете.

В методе TypeHandlerRegistry.register(String) сначала используется ResolverUtil (инструмент для поиска всех классов, соответствующих определенным условиям) для поиска всех классов, реализующих интерфейс TypeHandler, в указанном пакете. Затем исключаются анонимные классы, интерфейсы и абстрактные классы, и оставшиеся классы передаются методу register(Class) для дальнейшей обработки.

// Регистрация всех Java-классов в указанном пакете
public void register(String packageName) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    // Поиск всех подклассов TypeHandler в указанном пакете
    resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
    Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
    for (Class<?> type : handlerSet) {
        // Игнорирование вложенных классов, интерфейсов (включая package-info.java) и абстрактных классов
        if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
            // Регистрация обработчика типов
            register(type);
        }
    }
}
```##### Парсинг элемента `typeHandler`
Элемент `typeHandler` регистрируется в зависимости от наличия атрибута `jdbcType`. Если атрибут `jdbcType` отсутствует, регистрируется только класс обработчика типов. Если атрибут `jdbcType` присутствует, регистрируется комбинация класса обработчика типов и типа JDBC.

```java
// Регистрация обработчиков типов
if (javaTypeClass != null) {
    if (jdbcType == null) {
        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
    } else {
        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
    }
} else {
    typeHandlerRegistry.register(typeHandlerClass);
}
Парсинг аннотации MappedTypes

В методе register(Class) сначала извлекается аннотация MappedTypes (аннотация, используемая для указания Java-типов, которые обрабатывает обработчик типов). ```Если на процессоре типов присутствует аннотация MappedTypes, то регистрация будет продолжена с помощью метода `register(Class>, Class>)`. В противном случае, регистрация будет выполнена методом `register(TypeHandler)`.

Здесь код делит выполнение на ветки, и цепочка вызовов довольно длинная, что затрудняет запоминание, но это не столь важно, так как обе ветки в конечном итоге сойдутся в одном методе. Мы сначала разберем эти две ветки по порядку, сделаем пометку и запомним, что нам нужно вернуться к этому методу позже.

Сначала мы обрабатываем первую ветку, которая имеет аннотацию MappedTypes. ```Метод register(Class<?>, Class<?>) вызывает метод `getInstance(Class>, Class>)` для получения экземпляра `TypeHandler`, после чего делегирует выполнение методу `register(Class, TypeHandler extends T>)` для завершения регистрации. Метод `getInstance(Class>, Class<?>)` предназначен для создания экземпляра типа конвертера на основе заданного Java типа и типа обработчика конвертации:

/**
 * Получает обработчик конвертации на основе заданного Java типа и типа обработчика конвертации
 *
 * @param javaTypeClass    Java тип
 * @param typeHandlerClass Тип обработчика конвертации
 * @param <T>              Java тип
 */
@SuppressWarnings("unchecked")
public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    if (javaTypeClass != null) {
        try {
            // Получает конструктор обработчика конвертации с параметром типа Class
            Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
            // Создает экземпляр обработчика конвертации через конструктор
            return (TypeHandler<T>) c.newInstance(javaTypeClass);
        } catch (NoSuchMethodException ignored) {
            // ignored
        } catch (Exception e) {
            throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
        }
    }
    try {
        // Получает конструктор обработчика конвертации без параметров
        Constructor<?> c = typeHandlerClass.getConstructor();
        // Создает экземпляр обработчика конвертации через конструктор
        return (TypeHandler<T>) c.newInstance();
    } catch (Exception e) {
        throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
    }
}

Метод register(Class<T>, TypeHandler<?>extends T>)имеет интересную функциональность, которая заключается в том, чтобы преобразоватьClassв типTypeи затем передать управление методуregister(Type javaType, TypeHandler extends T> typeHandler)` для завершения регистрации. Здесь метод `register(Type javaType, TypeHandler extends T> typeHandler)` является тем конечным методом, о котором мы говорили ранее, когда ставили метки. Вместо того чтобы объяснять этот метод, мы продолжим обрабатывать вторую ветвь, которую мы рассматривали ранее: обработку случаев, когда отсутствует аннотация `MappedTypes`.Когда на указанном классе `TypeHandler` отсутствует аннотация `MappedTypes`, MyBatis сначала вызывает метод `getInstance(Class>, Class<?>), чтобы получить экземпляр TypeHandler, а затем делегирует метод register(TypeHandler)` для обработки.

В методе register(TypeHandler<T>) MyBatis сначала пытается получить аннотацию MappedTypes. Здесь важно отличать этот метод от register(Class<?>). В данном случае аннотация MappedTypes снова получается, и я проследил за вызовом этого метода, который используется только в методе register(Class<?>) за исключением юнит-тестов. Однако в методе register(Class<?>) этот вызов происходит только в случае отсутствия аннотации MappedTypes. Поэтому следующий фрагмент кода можно считать излишним:

boolean mappedTypeFound = false;
// Получаем аннотацию MappedTypes
MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
    for (Class<?> handledType : mappedTypes.value()) {
        // Регистрируем типы обработчиков на основе аннотации
        register(handledType, typeHandler);
        mappedTypeFound = true;
    }
}

Почему это можно считать излишним, но не абсолютно излишним? Потому что этот код оставляет возможность для прямого использования объекта TypeHandler для регистрации типов.

Возвращаясь к основной теме, метод register(TypeHandler<T>) предлагает три сценария регистрации для TypeHandler:

  • Если TypeHandler имеет аннотацию MappedTypes, то на основе значений аннотации MappedTypes вызывается метод register(Type, TypeHandler<? extends T>) для выполнения регистрации.- Если отсутствует аннотация MapperTypes, но класс TypeHandler наследует абстрактный класс TypeReference, то получается значение типа TypeHandler, и затем вызывается метод register(Type, TypeHandler<? extends T>) для выполнения регистрации.

  • Если ни одно из вышеперечисленных условий не выполняется, то вызывается метод register(Class<T>, TypeHandler<? extends T>), который преобразует null в тип Type, и затем вызывается метод register(Type, TypeHandler<? extends T>) для выполнения регистрации.

Перед тем как рассмотреть метод register(Type, TypeHandler<? extends T>), давайте сначала познакомимся с абстрактным классом TypeReference. TypeReference — это абстрактный класс, который используется для получения ссылок на параметризованные типы классов, например:

class TypeReferenceTest extends TypeReference<Integer> {
    
}

TypeReferenceTest наследует абстрактный класс TypeReference и указывает параметризованный тип как Integer. При вызове метода getRawType() у экземпляра TypeReferenceTest мы получаем Integer.class.

MyBatis определяет абстрактный класс BaseTypeHandler, который наследует TypeReference и реализует интерфейс TypeHandler. Этот класс является базовым классом для встроенных типов обработчиков в MyBatis.Определение BaseTypeHandler выглядит следующим образом:

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T>

Кроме того, BaseTypeHandler реализует методы по умолчанию, определенные в интерфейсе TypeHandler, добавляет базовую проверку данных и предоставляет новые абстрактные методы для реализации дочерними классами. Он также добавляет ссылку на конфигурационный класс Configuration MyBatis и предоставляет соответствующие методы установки.Переходим к методу register(Type, TypeHandler<? extends T>) класса TypeHandlerRegistry. Мы упоминали ранее, что есть один метод, который является альтернативным способом выполнения той же задачи, и это именно этот метод. Независимо от сценария, если мы можем вызвать этот метод, это означает, что у нас есть два параметра: тип Java (Type) и экземпляр обработчика типа (TypeHandler). Хотя первый параметр типа Java может быть null.

После входа в этот метод, сначала ищется аннотация MappedJdbcTypes на экземпляре TypeHandler.

Аннотация MappedJdbcTypes используется для указания JdbcType, который обрабатывается TypeHandler. Она имеет два параметра: массив JdbcType[] для указания набора JdbcType, которые обрабатываются этим TypeHandler, и булевский параметр includeNullJdbcType, который указывает, может ли этот TypeHandler обрабатывать null тип.

Если значение аннотации MappedJdbcTypes найдено, используется тип Java, JdbcType и экземпляр обработчика типа для регистрации обработчика типа. Также проверяется параметр includeNullJdbcType в аннотации MappedJdbcTypes для определения, может ли этот TypeHandler обрабатывать null значения.

Если значение аннотации MappedJdbcTypes не найдено, используется тип Java, null и экземпляр обработчика типа для регистрации.

В обоих этих ветвях регистрирование выполняется с помощью метода register(Type, JdbcType, TypeHandler<?>).Код метода register(Type, JdbcType, TypeHandler<?>) выглядит следующим образом:

    /**
     * Регистрация обработчика типов
     *
     * @param javaType java тип
     * @param jdbcType jdbc тип
     * @param handler  обработчик
     */
    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        if (javaType != null) {
            // Получение коллекции конвертеров jdbc типов для данного java типа
            Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
            if (map == null || map == NULL_TYPE_HANDLER_MAP) {
                map = new HashMap<>();
                // Регистрация java типа и отображения [jdbc тип и обработчик] в TYPE_HANDLER_MAP
                TYPE_HANDLER_MAP.put(javaType, map);
            }
            map.put(jdbcType, handler);
        }
        // Регистрация отображения типа обработчика и экземпляра обработчика
        ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
    }

Если переданный Java тип не равен null, то сначала получаем коллекцию конвертеров JDBC типов для данного Java типа. Эта коллекция использует JDBC типы в качестве ключей и конвертеры в качестве значений. Если коллекция не была получена или была получена NULL_TYPE_HANDLER_MAP, то создается новая коллекция, и связь Java типа и этой коллекции регистрируется в TYPE_HANDLER_MAP. Затем в коллекции регистрируется связь JDBC типа и обработчика.Затем, независимо от того, был ли передан Java тип или нет, связь типа обработчика и экземпляра обработчика регистрируется в ALL_TYPE_HANDLERS_MAP.

NULL_TYPE_HANDLER_MAP — это определение пустой коллекции.

TYPE_HANDLER_MAP — это коллекция, которая хранит отображение Java типа и [JDBC типа и обработчика].

ALL_TYPE_HANDLERS_MAP — это коллекция, которая хранит отображение типа обработчика и экземпляра обработчика.

Таким образом, после сложного процесса регистрации, задача регистрации обработчиков типов завершена.

Давайте еще раз рассмотрим основной процесс:

Сначала на основе базового пакета получаем все обработчики типов, которые нужно зарегистрировать. Затем используем аннотации или параметризованные типы для получения Java типа, который обрабатывается обработчиком. Затем преобразуем Java тип в Type. Затем используем аннотации для получения JDBC типа, который обрабатывается обработчиком. И наконец, регистрируем обработчик типов.

OK, процесс анализа элемента package завершен.

Разбор элемента typeHandler, прямая регистрация обработчиков типовПроцесс разбора элемента typeHandler значительно проще по сравнению с разбором элемента package. Сначала он получает определенные имена Java-типов, имена JDBC-типов и имена обработчиков типов на основе атрибутов javaType, jdbcType и handler. Затем он делегирует методу BaseBuilder#resolve(String) для разбора реальных Java-типов, JDBC-типов и типов обработчиков типов. После этого, на основе наличия или отсутствия Java-типов и JDBC-типов, определяется точка входа для регистрации псевдонимов.> Логика прямой регистрации обработчиков типов на основе разбора элемента TypeHandler в основном включена в логику регистрации обработчиков на основе package, поэтому здесь не будет повторного описания. Просто запомним основные шаги.
  • Есть Java-тип и JDBC-тип: Прямое вызов метода register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass). После преобразования typeHandlerClass в конкретный экземпляр, делегируется методу register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler), который преобразует Java-тип в тип Type. В конце вызывается метод register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) для выполнения реальной регистрации.

  • Есть Java-тип, но нет JDBC-типа: Вызов метода register(Class<?> javaTypeClass, Class<?> typeHandlerClass). После преобразования typeHandlerClass в конкретный экземпляр, делегируется методу register(Class<T> javaType, TypeHandler<? extends T> typeHandler), который преобразует Java-тип в тип Type. Затем вызывается метод register(Type javaType, TypeHandler<? extends T> typeHandler) для получения JDBC-типа, и в конце вызывается метод register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) для выполнения реальной регистрации.

  • Нет Java-типа: Этот сценарий соответствует логике регистрации обработчиков типов на основе package после обнаружения обработчика типов.

Таким образом, разбор элемента typeHandlers завершен, и основные подготовительные работы для MyBatis завершены.Следующим шагом будет разбор конфигурационных файлов мапперов MyBatis и инициализация SQL-среды.

Разбор конфигурационных файлов мапперов MyBatis и инициализация SQL-среды

В MyBatis для конфигурации мапперов (интерфейсов, определяющих операции DAO) обычно используются два способа: на основе аннотаций и на основе XML-конфигураций. Например:

На основе аннотаций

@Select({"SELECT * FROM blog"})
@MapKey("id")
Map<Integer, Blog> selectBlogsAsMapById();

На основе XML

List<Blog> selectBlogsFromXML();
<select id="selectBlogsFromXML" resultType="org.apache.ibatis.domain.blog.Blog">
    SELECT * FROM blog
</select>

Независимо от того, каким образом мы используем эти мапперы, нам необходимо указать MyBatis, где находятся эти мапперы и SQL-запросы, для этого мы используем элемент mappers в основном конфигурационном файле MyBatis.

DTD определение элемента mappers выглядит следующим образом:

<!ELEMENT mappers (mapper*, package*)>

Элемент mappers позволяет содержать ноль или более подэлементов mapper/package. Подэлемент package имеет обязательное свойство name, которое указывает на базовый пакет.

<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>

MyBatis при парсинге подэлемента package регистрирует все подходящие мапперы в этом пакете.Подэлемент mapper имеет три свойства, из которых одно должно быть заполнено, но не более одного. DTD определение подэлемента mapper выглядит следующим образом:

<!ELEMENT mapper EMPTY>
<!ATTLIST mapper
resource CDATA #IMPLIED
url CDATA #IMPLIED
class CDATA #IMPLIED
>

Где:

  • resource указывает на относительный путь к ресурсу (XML ресурсу)
  • url указывает на полный путь к ресурсу (XML ресурсу)
  • class указывает на полное имя класса маппераЭти четыре способа регистрации мапперов можно разделить на два типа: на основе экземпляра маппера и на основе XML файла.

package и class относятся к регистрации на основе экземпляра маппера, resource и url относятся к регистрации на основе XML файла. Парсинг элемента mappers в основном конфигурационном файле MyBatis осуществляется в методе XmlConfigBuilder#parseConfiguration(XNode root):

// !! Регистрация парсинга файлов MapperXml, соответствующих Dao
mapperElement(root.evalNode("mappers"));

Метод mapperElement определен следующим образом:

/**
 * Парсинг узла mappers в конфигурационном файле
 *
 * @param parent содержимое узла mappers
 */
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // Парсинг элемента package, регистрация всех мапперов в пакете
            // Например: <package name="org.mybatis.builder"/>
            // Проверка атрибута name в configuration>mappers>package
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                // Загрузка всех DAO-классов из пакета и регистрация
                configuration.addMappers(mapperPackage);
            } else {
                // Парсинг элемента mapper
            }
        }
    }
}
```                    // Определение атрибутов configuration>mappers>mapper
                    String resource = child.getStringAttribute("resource");
                    String url = child.getStringAttribute("url");
                    String mapperClass = child.getStringAttribute("class");
                    if (resource != null && url == null && mapperClass == null) {
                        // Парсинг ресурса с относительным классом
                        // Например: <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>                        // Только ресурс
                        ErrorContext.instance().resource(resource);
                        // Чтение указанного XML ресурса
                        InputStream inputStream = Resources.getResourceAsStream(resource);
                        // Продолжение парсинга XML файла маппера
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(
                                inputStream, /*Входной поток XML файла*/
                                configuration, /*Глобальная конфигурация пользователя*/
                                resource, /*Адрес конфигурационного файла ресурса*/
                                configuration.getSqlFragments() /*Уже существующие XML фрагменты кода*/
                        );
                        /*Парсинг этого XML файла*/
                        mapperParser.parse();
                    } else if (resource == null && url != null && mapperClass == null) {
                        // Парсинг ресурса с полным идентификатором ресурса
                        // Например: <mapper url="file:///var/mappers/AuthorMapper.xml"/>
                    }```markdown
// Только url
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
    // Парсинг ресурса с полным именем класса реализации маппера
    // Например: <mapper class="org.mybatis.builder.AuthorMapper"/>
}
```markdown
// Только mapperClass, добавляем объект напрямую
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
    // Одновременное указание нескольких элементов недопустимо
    throw new BuilderException("Элемент mapper может указывать только на url, resource или class, но не более одного.");
}
}
}

После получения всех дочерних узлов элемента mappers, MyBatis выполняет различные логические операции в зависимости от типа дочерних элементов mappers. ```其中 package 元素会交给 `Configuration#addMappers(String)` 来完成映射器的批量注册。

// Парсинг package элемента, регистрируем все маппера из пакета
// Например: <package name="org.mybatis.builder"/>
// Проверяем атрибут configuration>mappers>package
if ("package".equals(child.getName())) {
    String mapperPackage = child.getStringAttribute("name");
    // Загружаем все DAO-классы из пакета и регистрируем их
    configuration.addMappers(mapperPackage);
}
````mapper` элемент выполняет различные методы регистрации в зависимости от существующих атрибутов. Если присутствует только атрибут `class`, то вызывается метод `Configuration#addMapper(Class<T> type)` для регистрации маппера.

```java
if (resource == null && url == null && mapperClass != null) {
    // Парсинг полного имени класса, реализующего интерфейс маппера
    // Например: <mapper class="org.mybatis.builder.AuthorMapper"/>

    // Если указан только mapperClass, добавляем объект напрямую
    Class<?> mapperInterface = Resources.classForName(mapperClass);
    configuration.addMapper(mapperInterface);
} 

Обработка атрибутов resource и url отличается только способом загрузки XML-файлов. После получения входного потока XML-файла, MyBatis создает объект XMLMapperBuilder для регистрации маппера.

  • resource:
if (resource != null && url == null && mapperClass == null) {
    // Парсинг ссылки на ресурс по относительному пути класса
    // Например: <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
```    // Только ресурс
    ErrorContext.instance().resource(resource);
    // Чтение указанного XML-ресурсного файла
    InputStream inputStream = Resources.getResourceAsStream(resource);
    // Продолжение парсинга файла Mapper XML
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(
            inputStream, /*XML файловый вводный поток*/
            configuration, /*глобальная конфигурация пользователя*/
            resource, /*адрес конфигурационного файла ресурсов*/
            configuration.getSqlFragments() /*уже существующие XML блоки кода*/
    );
    /*Парсинг данного XML файла*/
    mapperParser.parse();
}
  • url
if (resource == null && url != null && mapperClass == null) {
    // Парсинг ресурса, используя полное имя ресурса
    // Например: <mapper url="file:///var/mappers/AuthorMapper.xml"/>
```    // Только URL
    ErrorContext.instance().resource(url);
    InputStream inputStream = Resources.getUrlAsStream(url);
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(
            inputStream /*XML файловый вводный поток*/,
            configuration /*глобальная конфигурация пользователя*/,
            url /*адрес конфигурационного файла ресурсов*/,
            configuration.getSqlFragments() /*уже существующие XML блоки кода*/
    );
    /*Парсинг данного XML файла*/
    mapperParser.parse();
}

Обратите внимание на обработку элементов resource и url в приведенном выше коде. Оба метода практически идентичны, отличие заключается только в способе загрузки файлов:resource загружает XML файл с помощью классового пути загрузки: InputStream inputStream = Resources.getResourceAsStream(resource);

в то время как url загружает XML файл с использованием URL: InputStream inputStream = Resources.getUrlAsStream(url);.

Оба метода используют Resources для загрузки XML ресурсного файла, Resources представляет собой утилиту, созданную Mybatis для упрощения доступа к ресурсам.


Возвращаясь к основной логике кода, рассмотрим процесс загрузки элементов package и mapper#class.

В методе configuration.addMappers(mapperPackage); объект Configuration делегирует массовую регистрацию отображений объекту Configuration#mapperRegistry.

Атрибут Configuration#mapperRegistry представляет собой экземпляр класса MapperRegistry.

MapperRegistry представляет собой регистратор отображений (DAO объектов) в Mybatis, который также отвечает за регистрацию отображений.При парсинге элемента package в методе Configuration#addMappers(String package) объект Configuration передает задачу объекту MapperRegistry через метод addMappers(String packageName). В методе addMappers(String packageName) MyBatis ограничивает классы-мапперы требованием реализовывать/наследоваться от класса Object. Это означает, что при загрузке мапперов все Java-классы в указанном пакете будут загружены как кандидаты на роль мапперов.

/**
 * Регистрация всех мапперов в указанном пакете
 * @since 3.2.2
 */
public void addMappers(String packageName) {
    // Регистрация всех Java-классов в указанном пакете
    addMappers(packageName, Object.class);
}

В методе addMappers(String packageName, Class<?> superType) MyBatis использует класс-инструмент ResolverUtil для поиска всех подклассов Object в указанном пакете. Затем все найденные подклассы передаются методу addMapper(mapperClass) для регистрации мапперов.

/**
 * Регистрация всех мапперов в указанном пакете, наследующихся от указанного типа
 * @since 3.2.2
 */
public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    // Поиск всех классов, наследующихся от указанного типа в указанном пакете
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<?>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
        // Выполнение регистрации маппера
        addMapper(mapperClass);
    }
}

В методе addMapper(mapperClass) сначала исключаются классы, которые не являются интерфейсами.Затем проверяется, зарегистрирован ли уже маппер для данного класса в текущем регистре. Если маппер не зарегистрирован, выполняется его анализ и регистрация.

MapperRegistry используется для поддержания реестра всех мапперов, который на самом деле представляет собой коллекцию карт с именем knownMappers, хранящую отображение типов мапперов и экземпляров прокси.Перед разбором, текущий маппер добавляется в реестр knownMappers в MapperRegistry, чтобы избежать повторного разбора.

Затем MapperRegistry создаёт объект MapperAnnotationBuilder для продолжения процесса разбора и загрузки мапперов.

Если разбор и загрузка маппера в MapperAnnotationBuilder завершаются неудачей, MapperRegistry удаляет отображение этого маппера из реестра knownMappers.

Обратите внимание на следующий метод, разбор элемента mapper#class также делегируется этому методу.

/**
 * Загружает маппер указанного типа
 * @param type тип маппера
 */
public <T> void addMapper(Class<T> type) {
```    if (type.isInterface()) {
        // Обрабатываем только интерфейсы
        if (hasMapper(type)) {
            // Обрабатываем только один раз
            throw new BindingException("Тип " + type + " уже известен MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            // Добавляем в известные прокси-мапперы, интерфейс маппера -> MapperProxyFactory прокси-объект
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            // Важно добавить тип до запуска парсера.
            // В противном случае, парсер маппера может попытаться автоматически привязаться.
            // Если тип известен, попытка привязки аннотаций не будет предпринята
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            // Выполняем разбор
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                // Удаляем интерфейс, если разбор завершился ошибкой
                knownMappers.remove(type);
            }
        }
    }
}

Для разбора элемента mapper#class метод configuration.addMapper(mapperInterface) делегируется методу addMapper(Class<T> type) объекта MapperRegistry.Таким образом, видно, что операции, выполненные перед разбором элемента package, имеют цель только для получения всех типов мапперов, которые необходимо зарегистрировать. После получения типа маппера, реальная работа по парсингу полностью передается классу MapperAnnotationBuilder. В данном контексте мы не будем углубляться в процесс парсинга MapperAnnotationBuilder, а вернемся к анализу элементов resource и url в контексте регистрации на основе XML-файлов.

Парсинг resource и url

Ранее мы упоминали, что код для парсинга resource и url практически идентичен, отличие заключается в том, что один из них использует относительный путь для получения XML-файлов с помощью класс-лоадера, а другой — URL для получения XML-файлов.

Оба элемента проходят через одинаковый процесс: получение значений resource/url, передача этих значений объекту Resources для получения входного потока XML-файла, а затем создание объекта XmlMapperBuilder для парсинга файла маппера.

XMLMapperBuilder mapperParser = new XMLMapperBuilder(
    inputStream /*входной поток XML-файла*/,
    configuration /*глобальная конфигурация MyBatis*/,
    url /*адрес конфигурационного файла*/,
    configuration.getSqlFragments() /*уже существующие XML-блоки*/
);

В конструкторе XmlMapperBuilder единственное, что может быть не знакомо, это configuration.getSqlFragments(). Configuration#sqlFragments представляет собой коллекцию, используемую для хранения блоков кода, и ее роль будет подробно объяснена в процессе дальнейшего парсинга.XmlMapperBuilder является реализацией класса BaseBuilder и предназначен для парсинга XML-файлов маппера MyBatis.

В процессе создания XmlMapperBuilder создается экземпляр парсера XPathParser.

Парсер XPathParser мы упоминали ранее. Он использует SAX для парсинга XML-документа, представленного входным потоком, и сохраняет его в виде DOM-дерева в объекте XPathParser.

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), // Создание парсера XPath
            configuration, // Конфигурация Mybatis
            resource, // Путь к ресурсу
            sqlFragments // Существующие блоки SQL
    );
}

Затем продолжается инициализация объекта XmlMapperBuilder путем вызова других конструкторов:

private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource); // Создание помощника для парсинга Mapper файла
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
}

MapperBuilderAssistant является инструментом-помощником для парсинга файлов mapper, фактически большинство операций по парсингу файлов mapper выполняются именно этим классом.

MapperBuilderAssistant также является экземпляром класса BaseBuilder, более подробные сведения о MapperBuilderAssistant будут рассмотрены позже.После завершения конструирования XmlMapperBuilder MyBatis начинает вызывать метод XmlMapperBuilder#parse() для парсинга файла mapper.

/* Парсинг данного XML файла */
mapperParser.parse();

В методе parse() класса XmlMapperBuilder сначала вызывается метод isResourceLoaded(String resource) объекта Configuration для проверки, был ли уже обработан указанный ресурс. Если ресурс еще не был обработан, будет выполнен полный процесс парсинга.> Атрибут Set<String> loadedResources объекта Configuration используется для поддержания коллекции всех уже загруженных ресурсов, основная цель — предотвратить повторное парсинг mapper файла.

Если указанный файл mapper еще не был обработан, MyBatis использует парсер XPathParser для парсинга всех элементов под корневым элементом mapper файла mapper в объекты XNode, чтобы завершить парсинг XML файла.

/**
 * Входной метод для парсинга файла MapperXml
 */
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        // Полный процесс парсинга будет выполнен только при первой загрузке конфигурационного файла

        // Чтение и конфигурация содержимого файла MapperXml
        configurationElement(parser.evalNode("/mapper"));

        // Запись текущего загруженного конфигурационного файла
        configuration.addLoadedResource(resource);
        // Привязка интерфейса DAO и текущего конфигурационного файла
        bindMapperForNamespace();
    }

    // Парсинг необработанных ResultMap
    parsePendingResultMaps();
    // Парсинг необработанных ссылок на кэш
    parsePendingCacheRefs();
    // Парсинг необработанных операторов
    parsePendingStatements();
}

После получения объекта XNode, соответствующего файлу mapper, MyBatis начинает вызывать метод configurationElement(XNode context) для парсинга объекта XNode, чтобы завершить конфигурацию одного маппера.Корневой элемент файла mapper MyBatis имеет имя mapper и обязательное свойство namespace. Это свойство используется для указания пространства имен, соответствующего текущему файлу mapper. Значение этого пространства имен уникально во всем MyBatis. Обычно мы задаем его как полное имя класса интерфейса маппера, и MyBatis автоматически связывает содержимое файла mapper с интерфейсом маппера (DAO). Элемент mapper содержит девять подэлементов:

  • cache-ref конфигурация ссылки на кэш в других пространствах имен.
  • cache конфигурация кэша.
  • resultMap конфигурация коллекции отображений результатов, определяющая, как значения столбцов базы данных преобразуются в объекты Java.
  • parameterMap конфигурация коллекции отображений параметров запроса, этот параметр сейчас устарел.
  • sql определение блока SQL-кода, который может использоваться для ссылки другими выражениями.
  • insert определение вставочного выражения.
  • update определение выражения обновления.
  • delete определение выражения удаления.
  • select определение выражения выборки.Процесс разбора этих подэлементов и их конкретные функции будут подробно объяснены в последующих статьях.

Сначала рассмотрим разбор элемента mapper.

При разборе элемента mapper, MyBatis сначала получает значение атрибута namespace (пространство имен) текущего элемента mapper. Значение namespace очень важно, так как оно используется для уникальной идентификации элемента mapper в среде MyBatis. Также уникальная идентификация всех подэлементов mapper зависит от этого значения. Именно благодаря наличию namespace, MyBatis обеспечивает возможность кросс-файловых ссылок.

Получив значение namespace, MyBatis использует его для инициализации пространства имен MapperBuilderAssistant, связанного с XmlMapperBuilder. Последующий процесс разбора будет зависеть от этого значения.

/**
 * Разбор конфигурации элемента mapper
 *
 * @param context элемент mapper
 */
private void configurationElement(XNode context) {
    try {
        // Получение текущего пространства имен (рабочего пространства) конфигурационного файла, обычно это полное имя класса DAO
        String namespace = context.getStringAttribute("namespace");
        if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Пространство имен маппера не может быть пустым");
        }
        // Конфигурация текущего пространства имен (рабочего пространства)
        builderAssistant.setCurrentNamespace(namespace);
    } catch (Exception e) {
        throw new BuilderException("Error configuring Mapper 'namespace'", e);
    }
}
```        // Разбор элемента ссылки на кэш
        cacheRefElement(context.evalNode("cache-ref"));
        // Разбор конфигурации кэша и установка кэша для текущего пространства имен, по умолчанию MyBatis использует PerpetualCache
        cacheElement(context.evalNode("cache"));
        // Разбор и регистрация элемента parameterMap
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));        // Разбор и регистрация элемента resultMap
        resultMapElements(context.evalNodes("/mapper/resultMap"));
```markdown
            // Парсинг и регистрация элемента Sql, здесь просто все фрагменты SQL читаются и помещаются в {@link Configuration#sqlFragments},
            // не выполняются дополнительные операции.
            sqlElement(context.evalNodes("/mapper/sql"));

            // Построение объявления операций CRUD
            buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

        } catch (Exception e) {
            throw new BuilderException("Ошибка при парсинге XML-файла маппера. Путь к файлу: '" + resource + "'. Причина: " + e, e);
        }
    }

В методе builderAssistant.setCurrentNamespace(namespace) производится базовая проверка значения пространства имен:

  /**
     * Установка текущего пространства имен для помощника парсинга Mybatis Mapper.
     * @param currentNamespace пространство имен
     */
    public void setCurrentNamespace(String currentNamespace) {
        if (currentNamespace == null) {
            throw new BuilderException("Элемент маппера требует указания атрибута namespace.");
        }

        // Проверка пространства имен, если текущее пространство имен уже определено и не совпадает с переданным пространством имен, будет выброшено исключение.
        if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
            throw new BuilderException("Неправильное пространство имен. Ожидается '" + this.currentNamespace + "' но найдено '" + currentNamespace + "'.");
        }
    }
```        // Установка значения
        this.currentNamespace = currentNamespace;
    }

Кэширование между пространствами имен в MyBatis

После завершения обработки пространства имен MyBatis сначала парсит содержимое элемента <cache-ref>.Перед этим стоит ознакомиться с некоторыми основными аспектами MapperBuilderAssistant помощника парсинга Mapper:

    /**
     * Текущее пространство имен для парсинга XML-файла маппера (рабочее пространство)
     */
    private String currentNamespace;
    /**
     * Соответствующий ресурсный файл
     */
    private final String resource;
    /**
     * Текущий используемый кэш
     */
    private Cache currentCache;
    /**
     * Существует ли необработанный элемент cache-ref
     */
    private boolean unresolvedCacheRef; // issue #676

У этого помощника много методов, но параметров немного, всего пять параметров: ```- currentNamespace глобально уникальный идентификатор XML-файла, который анализирует текущий помощник

  • resource путь к XML-файлу, который анализирует текущий помощник
  • currentCache объект кэша, используемый текущим помощником во время анализа XML-файла
  • unresolvedCacheRef наличие неразрешимых ссылок на кэш во время анализа текущим помощником

Можно приступить к анализу процесса обработки элементов в файле маппера Mybatis.

Элемент cache-ref в Mybatis используется для конфигурирования ссылки на объекты кэша из других пространств имен. Подробное описание объектов кэша будет дано в последующих статьях.DTD определение элемента cache-ref выглядит следующим образом:

<!ELEMENT cache-ref EMPTY>
<!ATTLIST cache-ref
namespace CDATA #REQUIRED
>

Элемент cache-ref имеет только один обязательный атрибут namespace. Значение этого атрибута представляет собой уникальный по всему приложению идентификатор объекта кэша, определенного в MyBatis. Методы генерации этого идентификатора будут описаны в последующих статьях.Анализ элемента cache-ref является относительно простым. MyBatis использует XPathParser, чтобы получить определение XNode элемента cache-ref из элемента mapper. Затем текущее пространство имен и уникальный идентификатор объекта кэша регистрируются в таблице регистрации ссылок на кэш cacheRefMap в конфигурации Configuration. Затем создается экземпляр CacheRefResolver с использованием текущего помощника построения маппера MapperBuilderAssistant и уникального идентификатора объекта кэша. Затем через метод resolveCacheRef() экземпляра CacheRefResolver завершается обработка ссылки на кэш.Если в методе resolveCacheRef() возникает исключение незавершенной обработки элемента (IncompleteElementException), то в конфигурации Configuration регистрируется экземпляр незавершенного CacheRefResolver.

// Парсинг кэш-ссылок
cacheRefElement(context.evalNode("cache-ref"));
/**
 * Парсинг узла cache-ref
 *
 * @param context узел cache-ref
 */
private void cacheRefElement(XNode context) {
    if (context != null) {
        // Обработка ссылок на кэши других пространств имен
        // Добавление кэш-ссылок
        configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
        // Создание объекта кэш-ссылки
        CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
        try {
            // Парсинг ссылок на кэши, установка используемого кэша для builderAssistant
            cacheRefResolver.resolveCacheRef();
        } catch (IncompleteElementException e) {
            // Если ссылка на кэш не существует (возможно, еще не загружена), добавляется метка незавершенной ссылки, которая будет завершена позже
            // Добавление метки незавершенной кэш-ссылки
            configuration.addIncompleteCacheRef(cacheRefResolver);
        }
    }
}

В приведенном выше тексте упоминается кэш-ссылочный парсер CacheRefResolver. CacheRefResolver является относительно простым объектом, который имеет два свойства и два метода. Среди свойств MapperBuilderAssistant assistant представляет собой помощника построения отображений для текущего XML-файла, а String cacheRefNamespace представляет собой уникальный идентификатор для ссылки на кэш.```Эти два параметра передаются в конструктор, а оставшийся метод для разрешения кеша ссылающихся объектов resolveCacheRef() делегирует реальную операцию разрешения методу `MapperBuilderAssistant` `useCacheRef(String namespace)`.``````markdown /**

  • Разрешение кеша ссылающихся объектов
  • @return кеш */ public Cache resolveCacheRef() { // Делегирование конфигурации вспомогательному объекту кеша return assistant.useCacheRef(cacheRefNamespace); }

В методе `MapperBuilderAssistant` `useCacheRef(String namespace)` MyBatis извлекает объект кеша из таблицы регистрации кешей `caches` объекта конфигурации `Configuration`. Если объект кеша существует, он присваивается текущему используемому полю кеша `currentCache` вспомогательного объекта `MapperBuilderAssistant`, а `unresolvedCacheRef` устанавливается в `true`, что означает, что объект кеша был получен. Если объект кеша не был получен, `unresolvedCacheRef` устанавливается в `false`, и выбрасывается исключение незавершенного элемента (`IncompleteElementException`).

Таким образом, разрешение ссылок на кеш завершено, и теперь следует разбор конфигурации кеша.

В XML-файле маппера MyBatis конфигурация кеша задается элементом `cache`, DTD определение элемента `cache` представлено ниже:

```Элемент cache может содержать ноль или более элементов `property`, а также имеет шесть необязательных атрибутов:

  • type указывает тип используемого объекта кеша.
  • eviction указывает стратегию очистки кеша.
  • flushInterval указывает интервал времени для сброса кеша, измеряется в миллисекундах.
  • size указывает размер содержимого кеша.
  • readOnly указывает, является ли кеш только для чтения.
  • blocking указывает, является ли кеш блокирующим.Коллекция элементов property используется для конфигурации пользовательских настроек кеша.

Для разбора элемента cache, в методе XMLMapperBuilder#configurationElement(XNode context) вызывается cacheElement(context.evalNode("cache")) для начала разбора.

Метод cacheElement(XNode context) сначала получает атрибут type элемента cache, который используется для маркировки типа кеша, используемого текущим маппером. Значение type может использовать механизм псевдонимов MyBatis, и в конструкторе Configuration регистрируется постоянный кеш с именем PERPETUAL. После получения имени типа кэша, используется TypeAliasRegistry#resolveAlias(String string) для разрешения типа экземпляра кэша. По умолчанию используется кэш с именем PERPETUAL (вечный кэш).После определения типа кэша, MyBatis получает значение свойства eviction для кэша, которое используется для определения механизма очистки кэша. Также значение eviction может использовать механизм псевдонимов MyBatis. В конструкторе без параметров класса Configuration по умолчанию предоставляются четыре механизма очистки кэша и их псевдонимы:

  • LRU, Least Recently Used (наименее недавно используемый): удаляет объекты, которые дольше всего не использовались.
  • FIFO, First In First Out (первый вошел, первый вышел): удаляет объекты в порядке их добавления в кэш.
  • SOFT, Soft Reference (мягкая ссылка): удаляет объекты на основе состояния сборщика мусора и правил мягких ссылок.
  • WEAK, Weak Reference (слабая ссылка): более активно удаляет объекты на основе состояния сборщика мусора и правил слабых ссылок.После получения имени типа механизма очистки кэша, используется TypeAliasRegistry#resolveAlias(String string) для разрешения типа экземпляра механизма очистки кэша. По умолчанию используется механизм очистки кэша с именем LRU.

После определения типа кэша и механизма очистки кэша, начинается последовательное чтение параметров.

Сначала получается значение конфигурации flushInterval, определяющее интервал обновления кэша. Затем получается значение свойства size, определяющее размер памяти, доступной для кэша. Затем получается значение свойства readOnly, определяющее тип кэша (читаемый или нечитаемый), по умолчанию установлено значение false. Затем получается значение свойства blocking, определяющее блокирующие свойства кэша, по умолчанию установлено значение false. В конце концов, получается пользовательское свойство property для кэша.

Важно отметить, что для свойства readOnly используется отрицательный оператор (!), так как это свойство фактически соответствует свойству readWrite класса CacheBuilder, о котором будет сказано позже.После получения этих свойств, они передаются в метод useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) помощника построения маппера MapperBuilderAssistant для настройки кэша. ``` /**

  • Парсинг элемента кэша
  • Настройка кэша */ private void cacheElement(XNode context) { if (context != null) { // Вторичный кэш, по умолчанию PERPETUAL String type = context.getStringAttribute("type", "PERPETUAL"); // Парсинг класса реализации кэша Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); // Политика очистки кэша // LRU – Least Recently Used: удаляет объект, который наименее использовался за последнее время. // // FIFO – First In First Out: удаляет объекты в порядке их добавления в кэш. // // SOFT – Soft Reference: удаляет объекты на основе состояния сборщика мусора и правил мягких ссылок. // // WEAK – Weak Reference: более активно удаляет объекты на основе состояния сборщика мусора и правил слабых ссылок. // Парсинг класса политики очистки кэша String eviction = context.getStringAttribute("eviction", "LRU"); // Парсинг класса политики очистки Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); // Установка интервала обновления Long flushInterval = context.getLongAttribute("flushInterval"); // Размер доступной памяти Integer size = context.getIntAttribute("size"); // Является ли кэш только для чтения, основное значение берется из параметра перед '! ' boolean readWrite = !context.getBooleanAttribute("readOnly", false); // Блокировка кэша } boolean blocking = context.getBooleanAttribute("blocking", false);
### Конфигурация пользовательских параметров```java
// Конфигурация пользовательских параметров
Properties props = context.getChildrenAsProperties();

// Использование нового кеша
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);

Метод useNewCache класса MapperBuilderAssistant

Метод useNewCache класса MapperBuilderAssistant создаёт новый кеш с использованием переданных параметров. После этого MyBatis регистрирует созданный кеш в таблице кешей (caches) конфигурации. Затем созданный кеш присваивается свойству currentCache текущего объекта MapperBuilderAssistant. Это означает, что кеш, определённый с помощью тега cache, имеет более высокий приоритет по сравнению с кешем, определённым с помощью тега cache-ref.

Конечно, это не означает, что кеш, определённый с помощью тега cache-ref, никогда не будет использоваться. В методе cacheElement(XNode context) есть проверка на наличие конфигурации тега cache.

private void cacheElement(XNode context) {
    if (context != null) {
        // Обработка элемента кеша...
    }
}

Создание кеша с помощью CacheBuilder

Кеш фактически создаётся с помощью CacheBuilder. CacheBuilder — это класс-строитель, используемый для создания объектов типа Cache. У этого класса есть следующие свойства:```java /**

  • Уникальный идентификатор кеша / private final String id; /*
  • Тип реализации кеша / private Class<? extends Cache> implementation; /*
  • Декораторы кеша / private final List<Class<? extends Cache>> decorators; /*
  • Размер кеша / private Integer size; /*
  • Интервал обновления кеша, в миллисекундах / private Long clearInterval; /*
  • Признак доступности кеша для чтения и записи / private boolean readWrite; /*
  • Параметры / private Properties properties; /*
  • Признак блокировки кеша */ private boolean blocking; Эти свойства соответствуют параметрам, которые можно использовать при конфигурации `CacheBuilder`. Конечно, основной код всё ещё находится в методе build(). Давайте вспомним код, использующий CacheBuilder в помощнике построения маппера, и я объясню, какую роль играет каждый параметр:
/ Регистрирует новый кэш для текущего пространства имен
Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class)) /* Конфигурация экземпляра кэша, по умолчанию PerpetualCache.class */
        .addDecorator(valueOrDefault(evictionClass, LruCache.class)) /* Конфигурация стратегии вытеснения, по умолчанию LruCache.class */
        .clearInterval(flushInterval) /* Конфигурация интервала очистки кэша */
        .size(size) /* Конфигурация размера памяти, доступной для кэша */
        .readWrite(readWrite) /* Конфигурация способности кэша к чтению и записи */
        .blocking(blocking) /* Конфигурация блокировки кэша */
        .properties(props) /* Конфигурация пользовательских свойств кэша */
        .build();

Теперь давайте рассмотрим процесс построения объекта Cache. В методе build() класса CacheBuilder сначала вызывается метод setDefaultImplementations(), который конфигурирует дефолтные реализации кэша. Если пользователь не указал конкретную реализацию кэша, то используется PerpetualCache. Если пользователь не указал стратегию очистки кэша, то используется LruCache.

    /**
     * Конфигурирует дефолтные реализации кэша
     */
    private void setDefaultImplementations() {
        if (implementation == null) {
            implementation = PerpetualCache.class;
            if (decorators.isEmpty()) {
                // Добавляем дефолтную стратегию очистки кэша
                decorators.add(LruCache.class);
            }
        }
    }
```После настройки дефолтных реализаций кэша, через рефлексию создается экземпляр класса кэша. Класс кэша должен иметь конструктор с одним параметром типа `String`. Затем через рефлексию пользовательские параметры передаются в экземпляр кэша. Определение того, являются ли параметры валидными, зависит от наличия соответствующих методов установки параметров в экземпляре кэша.

После внедрения пользовательских параметров в экземпляр кэша, производится обертывание экземпляра кэша. Для `PerpetualCache` используется текущий набор оберток `decorators` для обертывания экземпляра кэша. Каждая обертка пытается внедрить пользовательские параметры. После завершения обертывания с помощью `decorators`, производится дополнительное обертывание в соответствии с параметрами, указанными при создании `CacheBuilder`.

- Если пользователь указал параметр `size` и в кэше есть метод установки параметра `size`, то значение параметра `size` устанавливается.

- Если пользователь указал параметр `clearInterval`, то кэш обертывается оберткой с функцией периодической очистки кэша, интервал очистки равен значению параметра `clearInterval`.

- Если пользователь указал параметр `readWrite`, то кэш обертывается оберткой с функцией сериализации.

- Кэш обертывается оберткой с функцией логирования.- Кэш обёртывается обёрткой с функцией синхронизации.

- Если пользователь указал параметр `blocking` со значением `true`, то методы кэша становятся блокирующими. После завершения процесса упаковки возвращается упакованный экземпляр кэша `PerpetualCache`.

> Если вы не знакомы с концепцией упаковки обёртки, рекомендуется ознакомиться с паттерном декоратора в проектировании программного обеспечения.Для кэшей, которые не являются экземплярами `PerpetualCache` и не являются `LoggingCache`, будет добавлена обёртка с функцией логирования. До этого момента создание экземпляра кэша было завершено.
```java
public Cache build() {
    // Установка по умолчанию реализации кэша
    setDefaultImplementations();
    // Получение экземпляра кэша с помощью рефлексии и настройка его уникального идентификатора
    Cache cache = newBaseCacheInstance(implementation, id);
    // Настройка пользовательских параметров кэша с помощью рефлексии
    setCacheProperties(cache);
    // issue #352, не применять декораторы к пользовательским кэшам
    if (PerpetualCache.class.equals(cache.getClass())) {
        // Добавление декораторов кэша
        for (Class<? extends Cache> decorator : decorators) {
            // Экземпляр декоратора кэша
            cache = newCacheDecoratorInstance(decorator, cache);
            setCacheProperties(cache);
        }
        // Добавление стандартных декораторов к кэшу
        cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
        // Если кэш не имеет функции логирования, добавляем её
        cache = new LoggingCache(cache);
    }
    return cache;
}
```
Метод для стандартной настройки декораторов для `PerpetualCache`:
```java
private Cache setStandardDecorators(Cache cache) {
    try {
        // Получение метаданных для конфигурирования кэша
        MetaObject metaCache = SystemMetaObject.forObject(cache);
        if (size != null && metaCache.hasSetter("size")) {
            // Установка допустимого размера кэша
            metaCache.setValue("size", size);
        }
        if (clearInterval != null) {
            // Установка интервала очистки кэша
            cache = new ScheduledCache(cache);
        }
    }
    return cache;
}
```            ((ScheduledCache) cache).setClearInterval(clearInterval);
         }
         if (readWrite) {
             // Установка кэша с возможностью чтения и записи
             cache = new SerializedCache(cache);
         }
         // Добавление функции логирования
         cache = new LoggingCache(cache);
         // Добавление функции синхронизации
         cache = new SynchronizedCache(cache);
         if (blocking) {
             // Добавление блокировки для методов кэша
             cache = new BlockingCache(cache);
         }
         return cache;
     } catch (Exception e) {
         throw new CacheException("Ошибка при настройке стандартных декораторов кэша. Причина: " + e, e);
     }
 }

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

MyBatis源码中文翻译,包含学习时新增的单元测试 ### MyBatis源码翻译 MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了典型的JDBC代码冗长,同时也避免了样板化的数据访问对象代码。 #### 核心概念 - **SqlSessionFactory**: 用于创建SqlSession的工厂。 - **SqlSession**: MyBatis执行SQL语句的会话对象,是执行SQL语句并获取结果的最常用对象。 - **Mapper接口**: 定义了需要执行的SQL语句的接口。 - **XML映射文件**: 定义了MyBatis映射关系的XML文件。 ### 单元测试新增内容 为了更好地理解和测试MyBatis的功... Развернуть Свернуть
Apache-2.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/topanda-mybatis-3.git
git@api.gitlife.ru:oschina-mirror/topanda-mybatis-3.git
oschina-mirror
topanda-mybatis-3
topanda-mybatis-3
master