Подробное объяснение статьи длиной в 10 000 слов: анализ исходного кода SpringBoot-Mybatis.
Подробное объяснение статьи длиной в 10 000 слов: анализ исходного кода SpringBoot-Mybatis.

Оглавление

фон

Традиционный метод разработки Mybaits заключается в глобальной настройке инфраструктуры через mybatis-config.xml, например, кэше первого уровня, генераторе первичных ключей и т. д.

После выпуска SpringBoot, представив mybatis-spring-boot-starterПакет зависимостей,Может значительно снизить рабочую нагрузку,Достичь быстрого внедрения,Вы можете обратиться к предыдущемуиз Случай статьи:SpringBoot интегрирует Mybatis;Ниже мы объединяемSpringBootанализироватьMybatisиз Процесс инициализациии Процесс исполнения。

Предварительные знания

Сначала предварительные знания,Потому что автоматическая сборка SpringBoot упрощает многие конфигурации Mybatis.,Мы должны ввести процесс автоматического импорта Springbootizbean.

Подготовка 1. Загрузка аннотации @SpringBootApplication

Язык кода:javascript
копировать
Когда запустится Springboot, он прочитает файл META-INF\spring.factories.
Загрузите строку key=org.springframework.boot.autoconfigure.EnableAutoConfigurationiz в качестве имени класса.
(Запуск будет сотрудничать с содержимым в META-INF\spring-autoconfigure-metadata.properties, чтобы отфильтровать сцены, которые не соответствуют предыдущей сцене)

Подготовка 2: Создание экземпляра и последовательность инициализации компонента

Вы можете обратиться к статье:«Исходный код Spring: процесс загрузки компонента»

Разъясним несколько принципов:

  1. Создание экземпляра и инициализация компонента разделены на этапы.
  2. После создания экземпляра Bean, BeanFactory заранее отображается в памяти до завершения инициализации, чтобы облегчить решение проблемы циклических зависимостей.
  3. После создания экземпляра текущего компонента все инъекции зависимостей сначала будут завершены, а затем инициализированы (включая реализацию интерфейса afterProperties()/postProcessAfterInstantiation() и т. д.).

Файл 3: ObjectFactory、FactoryBean、BeanFactory

Анализ исходного кода

Согласно конвенции,Мы Анализ исходного кода, также следует два маршрута: определение регистрации фреймворка и время выполнения фреймворка.

1. Определение регистрации Framework Bean

1. Загрузка файлов Spring.factories и файлов интерфейса Mapper.

1.1 зависимость от помпа
Язык кода:javascript
копировать
<!-- Интеграция зависимостей, связанных с mybatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.2</version>
</dependency>

Сначала мы добавили зависимость mybatis-spring-boot-starter. Когда мы щелкнем по ней, мы увидим, что mybatis-spring-boot-starter также использует другие сторонние jar-пакеты:

Язык кода:javascript
копировать
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
  </dependency>
  <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
  </dependency>
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
  </dependency>
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
  </dependency>
</dependencies>

Обратите внимание, что существует также зависимость mybatis-spring-boot-autoconfigure.

1.2 Класс запуска UserServer
Язык кода:javascript
копировать
// ...Другие примечания
@SpringBootApplication
@MapperScan("com.bryant.mapper") // Mapper сканирует путь к пакету
public class UserServer {

   public static void main(String[] args) {
       SpringApplication.run(UserServer.class, args);
   }

}

Видим @SpringBootApplication, а это значит, что будет запущен следующий процесс автоматической сборки

  • Аннотация @SpringBootApplication изменяет класс запуска.
  • Аннотация @EnableAutoConfiguration изменена аннотация @SpringBootApplication
  • @Import(AutoConfigurationImportSelector.class) изменяет аннотацию @EnableAutoConfiguration.
  • AutoConfigurationImportSelectorвстречасуществовать@SpringBootApplicationСтартап-класс При запуске,Запустить автоматическую загрузку пакета jar из META-INF/spring.factories из всех классов-кандидатов конфигурации.

Мы вернулись к пакету зависимостей mybatis-spring-boot-autoconfigure и обнаружили, что файл Spring.factories действительно существует.

/mybatis-spring-boot-autoconfigure/2.1.2/mybatis-spring-boot-autoconfigure-2.1.2.jar!/META-INF/spring.factories

1.3 spring.factories
Язык кода:javascript
копировать
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

  • MybatisAutoConfigurationЭто основной класс автоматической настройки Mybatis.
1.4 Загрузка интерфейса Mapper MapperScannerRegistrar

Мы добавили такую ​​аннотацию @MapperScan в класс запуска

Язык кода:javascript
копировать
@MapperScan("com.bryant.mapper") // Mapper сканирует путь к пакету

Благодаря использованию @MapperScan будет инициировано введение других ресурсов.

Язык кода:javascript
копировать
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
}

@Import инициирует автоматическое введение класса MapperScannerRegistrar.

Язык кода:javascript
копировать
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
        BeanDefinitionRegistry registry, String beanName) {

      BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);

      // MapperFactoryBean установлен.
      Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
        if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
          builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
        }
       // ... опуская многие детали
      builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
      registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    }
}

  • BeanClass установлен = MapperScannerConfigurerизBeanDefinitionBuilder
  • Определите MapperScannerRegistrarизbean и установите атрибут MapperFactoryBeanClass: то есть фабрику MapperFactoryBean.
  • Определите компонент MapperScannerRegistrarizbean и установите атрибут basePackage: здесь мы напрямую используем определяемое пользователем значение пути com.bryant.mapper.
  • MapperScannerRegistrar реализует ImportBeanDefinitionRegistrarинтерфейс,Таким образом, метод будет запущен,Таким образом, вызывая метод RegisterBeanDefinitions,Завершите регистрацию BeanDefinitionRegistry определения компонента (просто добавьте BeanDefinition в BeanFactory).
  • Наконец запущен MapperScannerConfigurer из postProcessBeanDefinitionRegistry (ожидание дальнейших действий из【3.2】MapperScannerConfigurer)

2. Загрузка MybatisAutoConfiguration

Язык кода:javascript
копировать
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
}

Анализ кода:

  • @Configuration: объявите этот класс конфигурации в аннотации.
  • @ConditionalOnClass: условная аннотация, MybatisAutoConfiguration требует существования этих двух классов — SqlSessionFactory, SqlSessionFactoryBean.
  • @ConditionalOnSingleCandidate: условная аннотация,Определите, имеет ли контейнер Spring только одно определение typeDataSource.classizbeaniz.,Или их несколько,Но есть одно главное
  • @EnableConfigurationProperties: MybatisAutoConfiguration включает и загружает класс свойств конфигурации MybatisProperties.
  • @AutoConfigureAfter:
  • Автоматическая загрузка базы данных: DataSourcePoolMetadataProvidersConfiguration
  • Обработка динамического SQL: MybatisLanguageDriverAutoConfiguration используется в Spring Boot Автоматическая настройка в приложении MyBatis из LanguageDriver。LanguageDriver да MyBatis 中из Компонент, используемый для обработки SQL изDynamics в файлах сопоставления SQL заявление.
  • Сначала завершите загрузку 2 классов конфигурации — DataSourceAutoConfiguration, MybatisLanguageDriverAutoConfiguration.

Mybatis имеет множество этапов автоматической настройки и множество компонентов. Фактически, это можно сделать с помощью блок-схемы загрузки классов:

2.1、MybatisProperties

Прочитайте и проанализируйте конфигурацию, начиная с mybatis, например:

Язык кода:javascript
копировать
#Укажите Mapper из файла конфигурации из пути к папке Damapper из всех xml-файл.
mybatis.mapper-locations=classpath:mapper/*.xml

2.2、DataSourceAutoConfiguration

DataSourceProperties используется для чтения и анализа конфигурации, начиная с Spring.data, например:

Язык кода:javascript
копировать
#Конфигурация источника данных
spring.datasource.url = jdbc:mysql://localhost:3306/xxx?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=12345678
spring.datasource.name=xxx

3. Класс конфигурации MapperScannerRegistrarNotFoundConfiguration.

Здесь то же самое, что и 1,5. изMapperScannerRegistrarиззагрузка интерфейса мапперадавзаимоисключающие отношения,Так что это не происходит одновременно.

Как следует из названия, если связанный с MapperScan bean-компонент не определен, то выполняется класс конфигурации.

Язык кода:javascript
копировать
@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
    @Override
    public void afterPropertiesSet() {
      logger.debug(
          "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
        }
      }
}

  • @ConditionalOnMissingBean: если MapperFactoryBean и MapperScannerConfigurer не найдены, выполните инициализацию класса конфигурации.
  • Фактически, нет никаких других атрибутов членов, которые необходимо инициализировать.,Существует только один способ внедрить AutoConfiguredMapperScannerRegistrar для повторного сканирования зарегистрированных bean-компонентов.
3.1、AutoConfiguredMapperScannerRegistrar

Реализовано 2 интерфейса: BeanFactoryAware, ImportBeanDefinitionRegistrar.

Язык кода:javascript
копировать
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

  private BeanFactory beanFactory;

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    if (!AutoConfigurationPackages.has(this.beanFactory)) {
      logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
      return;
    }

    List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
    if (logger.isDebugEnabled()) {
      packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
    }

    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);
    builder.addPropertyValue("annotationClass", Mapper.class);
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
    BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
    Stream.of(beanWrapper.getPropertyDescriptors())
        // Need to mybatis-spring 2.0.2+
        .filter(x -> x.getName().equals("lazyInitialization")).findAny()
        .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
    registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
  }

  @Override
  public void setBeanFactory(BeanFactory beanFactory) {
    this.beanFactory = beanFactory;
  }

}

  • Интерфейс BeanFactoryAware: Фактически, среда Spring вызовет метод setBeanFactory и заполнит атрибуты BeanFactory, чтобы текущий компонент мог получить права BeanFactory изправа доступа
  • Интерфейс ImportBeanDefinitionRegistrar: зарегистрирует связанный изBeanDefinition.,Позволяет нам регистрировать дополнительные определения bean-компонентов.,Позволяет приложению динамически загружать bean-компоненты и управлять ими при запуске SpringContenxt. Здесь используется BeanDefinitionRegistry для сопоставления ScannerConfigurer Зарегистрироваться;
3.2、MapperScannerConfigurer

Поскольку класс MapperScannerConfigurer реализует интерфейс BeanDefinitionRegistryPostProcessor, он будет загружен до создания компонента и вызова его метода postProcessBeanDefinitionRegistry.

Язык кода:javascript
копировать
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
      if (this.processPropertyPlaceHolders) {
        // (1) Установите значения атрибутов
        processPropertyPlaceHolders();
      }

      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
      scanner.setAddToConfig(this.addToConfig);
      scanner.setAnnotationClass(this.annotationClass);
      scanner.setMarkerInterface(this.markerInterface);
      scanner.setSqlSessionFactory(this.sqlSessionFactory);
      scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
      scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
      scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
      scanner.setResourceLoader(this.applicationContext);
      scanner.setBeanNameGenerator(this.nameGenerator);
      // (2) здесь установлен MapperFactoryBeanClassдаMapperFactoryBean
      scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
      if (StringUtils.hasText(lazyInitialization)) {
        scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
      }
      scanner.registerFilters();
      // (3) Сканирование интерфейса картографа на основе значений атрибутов.
      scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, 
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

// (1) Установите значения атрибутов
private void processPropertyPlaceHolders() {
      Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
      if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
        // Получить определение Bean через BeanFactory
        BeanDefinition mapperScannerBean = 
        ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
            .getBeanDefinition(beanName);
        // Выньте значение атрибута «basePackage» и присвойте его.
        PropertyValues values = mapperScannerBean.getPropertyValues();
        this.basePackage = updatePropertyValue("basePackage", values);
      }
      this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
}

  • (1)processPropertyPlaceHolders: завершите присвоение значения атрибута «basePackage».
  • (2)setMapperFactoryBeanClass: заполните копию значения атрибута «mapperFactoryBeanClass» и объявите, что фабрика MapperFactoryBean используется для инициализации объекта Mapper.
  • (3) Здесь необходимо сканировать da на основе имени входящего пакета. Здесь this.basePackage говорит из базового пути к файлу картографа.
Отступление: откуда берется значение basePackage?

Ссылаясь на внедрение конфигурации интерфейса преобразователя в [1], MapperScannerRegistrar наконец добавляет BeanDefinition с именем MapperScannerRegistrar в BeanFactory и его значение basePackage = com.bryant.mapper.

Тогда существование【3】изMapperScannerConfigurer передаст beanName=MapperScannerRegistrar,Извлеките файл MapperScannerRegistrarизBeanDefinition из BeanFactory.,а затем извлечьbasePackageизценить = com.bryant.mapper。

Хорошо, теперь мы поговорим о том, как сканировать соответствующий файл пути к пакету.

3.3、ClassPathMapperScanner

Наследуя родительский класс ClassPathBeanDefinitionScanner, doScan выполняет внутренний обратный вызов метода родительского класса, а затем выполняет обработку определения компонента.

Язык кода:javascript
копировать
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // (1) Вызовите метод родительского класса ClassPathBeanDefinitionScanner#doScan.
  Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

  if (beanDefinitions.isEmpty()) {
    LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
        + "' package. Please check your configuration.");
  } else {
      // (2) Обработка коллекции определений bean-компонентов
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

//(1)org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   for (String basePackage : basePackages) {
       //Вот путь к классу сканирования и класс аннотации измэппера.
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      for (BeanDefinition candidate : candidates) {
         ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
         candidate.setScope(scopeMetadata.getScopeName());
         //Получить имя компонента
         String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
         if (candidate instanceof AbstractBeanDefinition) {
            postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
         }
         if (candidate instanceof AnnotatedBeanDefinition) {
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
         }
         //Это определяет, содержит ли beanFactory определение beanNameizbeaniz. Если нет, он войдет в ветку. В этой ветке нет ничего особенного. Просто добавьте определение beaniz в beanFactory.
         if (checkCandidate(beanName, candidate)) {
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            definitionHolder =
                  AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);
            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    // the mapper interface is the original class of the bean
    // but, the actual class of the bean is MapperFactoryBean
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
    definition = (GenericBeanDefinition) holder.getBeanDefinition();

    String beanClassName = definition.getBeanClassName();
    definition.setBeanClass(this.mapperFactoryBeanClass);

    if (!explicitFactoryUsed) {
      LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
  }
}

  • (1)findCandidateComponents:Прямо здесьда Сканировать путь к классамизmapperКласс аннотации
    • кандидаты: укажите имя пакета dacom.bryant.mapper.,Он будет преобразован в classpath*:com/bryant/mapper/**/*.class. Этот путь будет проанализирован и найден, и найденный класс будет возвращен как определение BeanDefinitioniz.
  • (2)postProcessBeanDefinition
    • Потому что интерфейс мапперда,даневозможно создать новый объектиз,Итак, здесь нам нужно дать BeanDefinition параметр Конструктор.
    • Долженbeanиз Режим внедрения атрибутов:AUTOWIRE_BY_TYPE

После описанных выше шагов написанный нами интерфейс Mapper будет просканирован платформой и готов к определению компонента. Итак, какой объект генерирует этот MapperBean?

3.4. MapperFactoryBean: инициализация объекта Mapper.

Давайте рассмотрим пункт 3 из [3] выше MapperScannerConfigurer;

В процессе сканера ClassPathMapperScanner одним из шагов является setMapperFactoryBeanClass, который присваивает фабричный класс MapperFactoryBean, что означает, что все последующие динамические прокси-объекты Mapper создаются на основе MapperFactoryBean#GetObject.

Вводим исходный код MapperFactoryBean

MapperFactoryBean наследует SqlSessionDaoSupport, который инкапсулирует SqlSessionTemplate.

Язык кода:javascript
копировать
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

    Override
    public T getObject() throws Exception {
      return getSqlSession().getMapper(this.mapperInterface);
    }

}

  • когда Spring Контейнер должен создать экземпляр Mapper Интерфейс из Bean , он позвонит MapperFactoryBean из getObject метод.
  • MapperFactoryBean Будет использоваться внутри MyBatis из SqlSession создать Mapper Интерфейс из Динамический прокси-объект。
  • MapperFactoryBean#getObject(): шаблон SqlSessionTemplate#getMapper
Входим в DefaultSqlSession
Язык кода:javascript
копировать
public class DefaultSqlSession implements SqlSession {
    private final Configuration configuration;
    private final Executor executor;

    @Override
    public <T> T getMapper(Class<T> type) {
      return configuration.getMapper(type, this);
    }
}

  • Разумеется, да инкапсулирует соединение Configuration и исполнитель Executor.
  • SqlSessionTemplate#getMapper: Суть даConfiguration#getMapper
Входим в Конфигурацию
Язык кода:javascript
копировать
public class Configuration {
    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      return mapperRegistry.getMapper(type, sqlSession);
    }
}

  • Конечно же, инкапсулирует реестр MapperRegistry.
  • Configuration#getMapper: Файл MapperRegistry#getMapper
Входим в MapperRegistry
Язык кода:javascript
копировать
public class MapperRegistry {

    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

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

  • Правда вот-вот выйдет наружу, и, наконец, раскрыт класс прокси-фабрики MapperProxyFactory.
  • Через MapperProxyFactory создайте прокси-объект Mapper.
  • MapperProxyFactory.newInstance(sqlSession): по существу вызывает MapperProxyFactory.
Входим в MapperProxyFactory
Язык кода:javascript
копировать
public class MapperProxyFactory<T> {

  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

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

  • Наконец, был создан новый MapperProxy.
  • и передайте Proxy.newProxyInstance,Прокси-объект MapperImpl настроен.,Прокси-объект MapperImpl реализует все интерфейсы Mapper.,И добейтесь логического улучшения с помощью MapperProxy
Входим в MapperProxy
Язык кода:javascript
копировать
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private final SqlSession sqlSession;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      try {
        if (Object.class.equals(method.getDeclaringClass())) {
          return method.invoke(this, args);
        } else {
          return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
}

Анализ кода:

  1. существоватьinvokeв методе,Сначала определите, принадлежит ли бывший прокси-объект к классу даObject.,Если да,Затем напрямую вызовите метод переднего объекта, когда,в противном случае,Затем используйте объект кэшизInvoker для вызова изметода целевого объекта.
  2. Метод улучшения вызоваиз Основные шагидаcachedInvoker метод,да используется для получения объекта кэшиз Инвокер,Если объект существует не существует в кэше,Затем создайте новый объект изInvoker.,И кэшируйте это.

cachedInvoker

Язык кода:javascript
копировать
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
  try {
    return methodCache.computeIfAbsent(method, m -> {
      if (m.isDefault()) {
       // ...здесь да против Интерфейса иззакрытый метод(defaultметод、статический метод и т. д.)
      } else {
      // Путь по умолчанию — издаPlainMethodInvoker.
        return new PlainMethodInvoker(
            new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      }
    });
  } catch (RuntimeException re) {
    Throwable cause = re.getCause();
    throw cause == null ? re : cause;
  }
}

  • Обычно все интерфейсы являются общедоступными.,Запустит создание PlainMethodInvokeriz
  • Ядро PlainMethodInvoker из,инкапсуляция MapperMethodиз,MapperMethod также является основным методом Mapper.
Входим в PlainMethodInvoker
Язык кода:javascript
копировать
private static class PlainMethodInvoker implements MapperMethodInvoker {
  private final MapperMethod mapperMethod;

  public PlainMethodInvoker(MapperMethod mapperMethod) {
    super();
    this.mapperMethod = mapperMethod;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return mapperMethod.execute(sqlSession, args);
  }
}

  • PlainMethodInvoker инкапсулирует MapperMethod, и его вызов фактически также вызывает MapperMethod.
Входим в MapperMethod

Конструктор

Язык кода:javascript
копировать
public class MapperMethod {
    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
      this.command = new SqlCommand(config, mapperInterface, method);
      this.method = new MethodSignature(config, mapperInterface, method);
    }
}

  • MapperMethod создаст внутреннюю команду SqlCommand, а также MethodSignature.

Выше показано, как Mybatis инкапсулирует код SqlSession.,это действительно сложно,Объект из цепочки вызовов очень длинный,但нас用一张图来Подвести итоги предыдущего дня:

4. Загрузка sqlSessionFactory

Язык кода:javascript
копировать
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
  //...Опустить настройки атрибутов
  return factory.getObject();
}

Если SqlSessionFactory отсутствует, этот метод вызывается по умолчанию.

5. Загрузка sqlSessionTemplate.

Язык кода:javascript
копировать
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  ExecutorType executorType = this.properties.getExecutorType();
  if (executorType != null) {
    return new SqlSessionTemplate(sqlSessionFactory, executorType);
  } else {
    return new SqlSessionTemplate(sqlSessionFactory);
  }
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {
  this.sqlSessionFactory = sqlSessionFactory;
  this.executorType = executorType;
  this.exceptionTranslator = exceptionTranslator;
  this.sqlSessionProxy = (SqlSession) newProxyInstance(
              SqlSessionFactory.class.getClassLoader(),
              new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}

  • Если SqlSessionTemplate отсутствует, этот метод вызывается по умолчанию.
  • SqlSessionTemplateиз Конструктор,Наконец, мы вызвали класс Proxy из динамического прокси.,Создайте прокси-объект для SqlSessioniz.
  • существовать JDK В динамическом прокси создайте из класса прокси $Proxy наследство Proxy и осознать SqlSession интерфейс, когда при вызове прокси-класса из метода он войдет в перехватчик SqlSessionInterceptor из invoke в методе
5.1. Усилитель SqlSessionInterceptor.
Язык кода:javascript
копировать
SqlSessionInterceptor

private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
      Object result = method.invoke(sqlSession, args);
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        // force commit even on non-dirty sessions because some databases require
        // a commit/rollback before calling close()
        sqlSession.commit(true);
      }
      return result;
    } catch (Throwable t) {
      Throwable unwrapped = unwrapThrowable(t);
      if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
        // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        sqlSession = null;
        Throwable translated = SqlSessionTemplate.this.exceptionTranslator
            .translateExceptionIfPossible((PersistenceException) unwrapped);
        if (translated != null) {
          unwrapped = translated;
        }
      }
      throw unwrapped;
    } finally {
      if (sqlSession != null) {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

Логика агента SqlSessionInterceptor относительно сложна. Ниже мы используем рисунок для представления его процесса:

5.2. Потокобезопасность SqlSession.

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

Язык кода:javascript
копировать
public abstract class TransactionSynchronizationManager {
    private static final ThreadLocal<Map<Object, Object>> resources =
          new NamedThreadLocal<>("Transactional resources");

    /**
     * Gets an SqlSession from Spring Transaction Manager or creates a new one if needed. Tries to get a SqlSession out of
     * current transaction. If there is not any, it creates a new one. Then, it synchronizes the SqlSession with the
     * transaction if Spring TX is active and <code>SpringManagedTransactionFactory</code> is configured as a transaction
     * manager.
     */
    public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
        PersistenceExceptionTranslator exceptionTranslator) {

      SqlSessionHolder holder = (SqlSessionHolder) 
              TransactionSynchronizationManager.getResource(sessionFactory);

      SqlSession session = sessionHolder(executorType, holder);
      if (session != null) {
        return session;
      }

      LOGGER.debug(() -> "Creating a new SqlSession");
      session = sessionFactory.openSession(executorType);

      registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

      return session;
    }

    @Nullable
    public static Object getResource(Object key) {
       Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
       Object value = doGetResource(actualKey);
       return value;
    }

    @Nullable
    private static Object doGetResource(Object actualKey) {
       Map<Object, Object> map = resources.get();
       if (map == null) {
          return null;
       }
       Object value = map.get(actualKey);
       // Transparently remove ResourceHolder that was marked as void...
       if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
          map.remove(actualKey);
          // Remove entire ThreadLocal if empty...
          if (map.isEmpty()) {
             resources.remove();
          }
          value = null;
       }
       return value;
    }
}

Анализ кода:

5.3. Резюме

После описанного выше процесса sqlSessionTemplate был построен, и теперь пришло время вступить в игру sqlSessionTemplate!

Однако, будучи простой и эффективной платформой ORM, Mybatis, конечно же, не будет напрямую предоставлять пользователям возможность вызова sqlSessionTemplate. Вместо этого он будет инкапсулировать его слой за слоем, что сделает его более соответствующим привычкам развития бизнеса.

2. Среда выполнения Framework Bean

Когда мы хотим запросить SQL, как нам это сделать? Выполните ли вы следующие действия:

  • @Autowired внедрить UserMapper
  • Используйте UserMapperизinsert/update/delete/select интерфейс
  • Динамическое сопоставление с выполнением Mapper.xmlizsql.
  • Получить результат сборки и вернуть

Выше мы уже знаем, что когда вызывается Mapper, внедренный @Autowired, он в конечном итоге запускает выполнение динамического прокси-класса. После приведенного выше анализа метод каждого объекта Mapper в конечном итоге будет преобразован в MapperMethod, и существует метод; самый уровень цепочки инкапсуляции сложных объектов, как показано ниже:

1. Выполнение MapperMethod

Упомянутый выше MapperMethod — это место, где выполняется окончательный SQL. Основной метод — это метод выполнения:

Язык кода:javascript
копировать
org.apache.ibatis.binding.MapperMethod#execute

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

Анализ кода:

  • INSERT
    • ConvertArgsToSqlCommandParam: используйте ParamNameResolver для обработки фактического списка параметров.
    • sqlSessionизinsert был вызван
    • rowCountResult: тип возвращаемого значения для преобразования (очень удобный для пользователя).,Рамка понравилась)
  • UPDATE
    • Процесс обработки аналогичен INSERT.
    • sqlSessionизupdate был вызван
  • DELETE
    • Процесс обработки аналогичен INSERT.
    • sqlSessionизdelete был вызван
  • SELECT
    • ExecuteWithResultHandler: возвращаемое значение обработки — Void и содержит ResultHandler.
    • выполнитьForMany: обработать возвращаемое значение как коллекцию или массив.
    • ExecuteForMap: возвращаемое значение обработки — Map
    • ExecuteForCursor: возвращаемое значение обработки — Cursor
    • Если оставшееся из остается, возвращается один объект из.

Думаете, на этом анализ заканчивается? Нет, там еще и выполнение sqlSession!

2. Выполнение SqlSession

DefaultSqlSession реализует методы, определенные интерфейсом SqlSession, и предоставляет перегрузки для каждой операции с базой данных, как показано на рисунке ниже:

2.1、insert

Ну а нижний слой оказывается называется обновлением. .

Язык кода:javascript
копировать
@Override
public int insert(String statement, Object parameter) {
  return update(statement, parameter);
}

2.2、update
Язык кода:javascript
копировать
@Override
public int update(String statement, Object parameter) {
  try {
    dirty = true;
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.update(ms, wrapCollection(parameter));
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

  • GetMappedStatement
  • Операция обновления через Executor
2.3、delete

Ну, нижний уровень изначально также назывался обновлением. .

Язык кода:javascript
копировать
@Override
public int delete(String statement) {
  return update(statement, null);
}

2.4、select
Язык кода:javascript
копировать
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    executor.query(ms, wrapCollection(parameter), rowBounds, handler);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

  • GetMappedStatement
  • Выполнение операций запроса через Executor

Думаете, на этом анализ заканчивается? Нет, есть еще и казнь Палача!

3. Исполнение исполнителя

BaseExecutor — это класс шаблона, который содержит основные операции с базой данных.

3.1、update
Язык кода:javascript
копировать
org.apache.ibatis.executor.BaseExecutor#update

@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  clearLocalCache();
  return doUpdate(ms, parameter);
}

SimpleExecutor является исполнителем по умолчанию. Мы в основном фокусируемся на нем, продолжаем уделять внимание методу doUpdate и продолжаем отлаживать содержимое SimpleExecutor.

Язык кода:javascript
копировать
org.apache.ibatis.executor.SimpleExecutor#doUpdate

/**
 * Выполнить операцию обновления метода реализации.
 * 
 * @param ms Объект оператора сопоставления, содержащий информацию о выполнении SQL.
 * @param parameter Параметры, передаваемые в SQLиз
 * @return Количество строк, на которые влияет операция обновления.
 * @throws SQLException Если во время выполнения возникает исключение SQL
 */
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Statement stmt = null;
  try {
    // Получите объект конфигурации, используемый для создания StatementHandler.
    Configuration configuration = ms.getConfiguration();
    // Создайте StatementHandler, отвечающий за выполнение операторов SQL.
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    // Подготовьте оператор SQL и верните объект Statement
    stmt = prepareStatement(handler, ms.getStatementLog());
    // Выполняет операцию обновления и возвращает количество затронутых строк.
    return handler.update(stmt);
  } finally {
    // Закрыть ресурс заявления
    closeStatement(stmt);
  }
}

Анализ кода:

  • Получить информацию о конфигурации
  • Создайте новый StatementHandler.
  • ПодготовкаStatement: предварительная обработка
  • handler.update(stmt):Выполняет операцию обновления и возвращает количество затронутых строк.

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

3.2、select

BaseExecutor#query: предоставляет базовые функции управления кэшем и транзакциями.

Язык кода:javascript
копировать
org.apache.ibatis.executor.BaseExecutor#query

/**
 * Запросить данные и вернуть список результатов
 * 
 * @param ms Объект оператора сопоставления, включая шаблон SQL и другую информацию о конфигурации.
 * @param parameter Параметры, передаваемые в оператор SQL
 * @param rowBounds Используется для пейджингового запроса из диапазона строк.
 * @param resultHandler Обработчик результатов, используемый для обработки результатов запроса.
 * @return Возвращает список, содержащий результаты запроса.
 * @throws SQLException Если во время запроса возникает исключение SQL
 * 
 * Этот метод является одним из основных методов выполнения запросов. Он отвечает за выполнение SQL-запросов на основе предоставленных параметров и информации о конфигурации.
 * И передать результат обработчику результатов для обработки или инкапсулировать его в список и вернуть.
 */
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  // Подготовьте оператор SQL и сгенерируйте кэш-ключ CacheKey
  BoundSql boundSql = ms.getBoundSql(parameter);
  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  // Выполнить запрос и вернуть результаты
  return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}


/**
 * Запросить базу данных и вернуть список результатов
 * Этот метод выполняет основную часть запроса и отвечает за обработку кэша, запроса к базе данных и обработку результатов.
 * 
 * @param ms Объект оператора сопоставления, содержащий информацию о конфигурации запроса из SQLи.
 * @param parameter Параметры запроса могут быть любыми
 * @param rowBounds Объект диапазона строк, используемый для пейджинговых запросов.
 * @param resultHandler Обработчик результатов, используемый для обработки результатов запроса.
 * @param key Ключ кэша, используемый для идентификации запроса кэша
 * @param boundSql Подготовьте объект оператора изSQL
 * @return Список результатов запроса, тип элемента – E.
 * @throws SQLException Если во время запроса возникает ошибка доступа к базе данных
 */
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {  
  // существующий стек запроса пуст и оператор сопоставления требует обновления кэша, очистите локальный кэш
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }

  List<E> list;
  try {
    // Увеличьте глубину стека запросов, чтобы рекурсивные запросы не вызывали проблем.
    queryStack++;
    // Попробуйте получить результат из локального кэша. Если resultHandler равен нулю и сохраните существующий результат, используйте результат кэша.
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    // Если результаты получены из кэша, обработать выходные параметры локального кэша.
    if (list != null) {
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      // Если результатов в кэше нет, запросите данные из базы данных
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    // Уменьшите глубину стека запросов и убедитесь, что ресурсы освобождаются правильно.
    queryStack--;
  }

  // Обработка отложенной загрузки и локального кэша, когда стек запросов пуст
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
      // Если уровень операторов включен, данные уровня сеанса должны быть очищены.
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}


/**
 * Запросить данные из базы данных и сохранить результаты в существующем кэше.
 * Если тип объявлен CALLABLE, выходные параметры также сохраняются в существующем кэше.
 * 
 * @param ms Объект оператора сопоставления, содержащий информацию и конфигурацию SQL.
 * @param parameter параметры запроса
 * @param rowBounds Диапазон строк, используемый для пейджинга
 * @param resultHandler Обработчик результатов, используемый для обработки результатов запроса.
 * @param key Ключ кэша, используемый для хранения и получения данных в кэше.
 * @param boundSql Скомпилированный оператор SQL и сопоставление параметров
 * @return Список результатов запроса
 * @throws SQLException Если во время запроса возникает исключение SQL
 */
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // Сначала поместите заполнитель выполнения в локальный кэш, чтобы указать, что запрос выполняется.
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        // Выполнить запрос
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        // После завершения запроса удалите заполнитель выполнения из локального кэша.
        localCache.removeObject(key);
    }
    // Поместите результаты запроса в локальный кэш.
    localCache.putObject(key, list);
    // Если тип объявлен CALLABLE, выходные параметры помещаются в локальный кэш.
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

/**
 * Выполнить запросметод
 * 
 * @param ms Объект оператора сопоставления, содержащий оператор SQL и информацию о конфигурации.
 * @param parameter Параметры, передаваемые в оператор SQL
 * @param rowBounds Используется для постраничного запроса параметров, определяющих начальную и конечную запись.
 * @param resultHandler Обработчик результатов, используемый для обработки результатов запроса.
 * @param boundSql Скомпилированный объект оператора изSQL содержит окончательное сопоставление параметров изSQL.
 * @return Вернуться к списку результатов запроса,Тип элемента в списке определяется общим E
 * @throws SQLException Если во время запроса возникает исключение SQL
 * 
 * Этот метод отвечает за выполнение SQL-запроса в операторе сопоставления и обработку результатов запроса через StatementHandler.
 * Он инкапсулирует процесс запроса,включая созданиеStatementHandler、Подготовьте оператор SQL、Выполнить запроси Закрыть ресурс
 */
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    // Получите объект конфигурации. Конфигурация содержит глобальную информацию о конфигурации.
    Configuration configuration = ms.getConfiguration();
    // Создайте StatementHandler для обработки выполнения инструкций SQL и обработки набора результатов.
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    // Подготовьте операторы SQL, включая создание операторов, настройку параметров и т. д.
    stmt = prepareStatement(handler, ms.getStatementLog());
    // Выполнить запрос и вернуть результаты
    return handler.query(stmt, resultHandler);
  } finally {
    // Закрытие заявления и выпуск ресурсов
    closeStatement(stmt);
  }
}

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

Мы можем использовать блок-схему для анализа

Кэш 1-го уровня — CacheKey

CacheKey в приведенном выше коде на самом деле является кешем первого уровня, используемым по умолчанию при открытии объекта SqlSession. Его жизненный цикл такой же, как и у SqlSession.

Язык кода:javascript
копировать
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  CacheKey cacheKey = new CacheKey();
  cacheKey.update(ms.getId());
  cacheKey.update(rowBounds.getOffset());
  cacheKey.update(rowBounds.getLimit());
  cacheKey.update(boundSql.getSql());

  for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
  return cacheKey;
}

Объект CacheKey состоит из следующих частей: идентификатор, смещение и ограничение, sql, предоставленные пользователем параметры и идентификатор среды.

Потокобезопасность кэша первого уровня

Кэш первого уровня в mybatis хранится в hashMap. Причина, по которой потокобезопасные коллекции не используются, заключается главным образом в том, что SqlSession небезопасен для потоков, поэтому в этом нет необходимости.

иSqlSessionПравила безопасности потоковдапередается каждому триггерному поведениюизclearCache()метод,когдакаждое исполнениеsqlПосле завершения,Весь контент будет удален,Таким образом, вы сможете избежать проблем с потокобезопасностью, вызванных кэшем!

Знакомство с другими исполнителями

тип

Функции

кэш

SimpleExecutor

Сосредоточьтесь на реализации 4 основных методов

Кэш 1 уровня

ReuseExecutor

Уменьшите накладные расходы на предварительную компиляцию SQL, создайте и уничтожьте накладные расходы на объекты Statement и улучшите производительность.

Кэш 1 уровня

BatchExecutor

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

Кэш 1 уровня

CachingExecutor

Увеличение Кэш Уровень операторов L2 из служебных данных,Уменьшите количество выполнений базы данных,Улучшите производительность

Кэш 1 уровня Кэш L2

Далее мы сосредоточимся на CachingExecutor.

CachingExecutor

CachingExecutor инкапсулирует объект Executor для выполнения операций с базой данных и управляет объектом L2изTransactionalCacheManager.

Кэш L2

Кэш L2: Уровень приложения изкэш, состоящий из TransactionalCacheManager&TransactionalCacheДва компонента достижения:

  • TransactionalCache: унаследовал интерфейс Cache, сохранение определенного SqlSession и определенной транзакции требует добавления из Кэша. Данные L2
  • TransactionalCacheManager: используется для управления CachingExecutor из Кэша. Объект L2, а именно ключ.
Язык кода:javascript
копировать
public class CachingExecutor implements Executor {
    private final Executor delegate;
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
        throws SQLException {
        //【1】Запрос пространства имен, соответствующего существуиз Кэш L2
      Cache cache = ms.getCache();
      if (cache != null) {
        //【2】
        flushCacheIfRequired(ms);
          //【3】
        if (ms.isUseCache() && resultHandler == null) {
          //【4】
          ensureNoOutParams(ms, boundSql);
            //【5】
          List<E> list = (List<E>) tcm.getObject(cache, key);
            //【6】
          if (list == null) {
            list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
            tcm.putObject(cache, key, list); // issue #578 and #116
          }
          return list;
        }
      }
        //【7】
      return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
}

Анализ кода:

  • 【1】ms.getCache()
    • Получите объект кэша: запросите пространство имен, соответствующее существующему из Кэш. L2 (да включать ли)
  • 【2】flushCacheIfRequired(ms) -Кэш L2Когда его нужно чистить??Прямо здесьда Ответ:!
    • в кэшкеше есть данные и MappedStatement из Конфигурацию необходимо очистить кэш.
  • 【3】ms.isUseCache()
    • Проверьте да, использовать ли кэш: if MappedStatement Настроено на использование кэши нет ResultHandler, затем продолжайте выполнять следующие шаги.
  • 【4】EnsureNoOutParams: Убедитесь, что выходные параметры отсутствуют: вызовите метод обеспеченияNoOutParams, чтобы убедиться, что оператор запроса не имеет выходных параметров.
  • 【5】tcm.getObject: уничтожить Кэш L2
  • 【6】Попробуйте начать с Кэш L2Получить результаты запроса в。есликэш Чжунцуньсуществоватьрезультат,затем вернитесь напрямую。
    • Если в кэше нет результата, делегируйте delegate Выполнить запрос,И сохраните результат в кэш.
  • 【7】Кэш не запущен L2,Затем напрямую вызовите базовый изExecutor,Наконец, верните результаты запроса.
Схема взаимоотношений Кэш L2 выглядит следующим образом:
Структура данных Кэш L2из

Сборка MyBatisиз Кэш L2реализована по шаблону декоратораиз

Цепочка украшений: SynchronizedCache -> LoggingCache -> SerializedCache -> LruCache -> PerpetualCache

Язык кода:javascript
копировать
SynchronizedCache: синхронизированный кеш,Реализация относительно проста,Используйте Synchronized напрямую для изменения метода.
LoggingCache: функция ведения журнала, класс оформления, используемый для записи частоты попаданий. Если включен режим DEBUG, будет выведен журнал частоты попаданий.
SerializedCache: функция сериализации, сериализует значение и сохраняет его в кэше. Эта функция используется для возврата копии экземпляра изCopy для сохранения потокобезопасности.
LruCache: принимает реализацию алгоритма Lru из Cache, удаляя наименее использованный ключ/значение.
PerpetualCache: как самый основнойизкэшдобрый,Первый этаж Реализация относительно проста,Используйте HashMap напрямую

Кэш L2из потокобезопасности

Всякий раз, когда выполняется insert、update、delete,flushCache=true Когда, Кэш L2 будет очищен.

Кэш L2издача данных

Кэш L2 должна быть отправлена ​​существующая транзакция сеанса, прежде чем она вступит в силу! После того, как существование найдет результат, он вызовет SqlSession из commit метод отправки.

Язык кода:javascript
копировать
public class TransactionalCache implements Cache {
    private final Cache delegate;
    private boolean clearOnCommit;
    private final Map<Object, Object> entriesToAddOnCommit;
    private final Set<Object> entriesMissedInCache;

    public void commit() {
        // 【1】Перед отправкой транзакции очистите существующие. Грязные данные L2
      if (clearOnCommit) {
        delegate.clear();
      }
      //【2】Сохраняем коллекцию записейToAddOnCommit в Кэш L2
      flushPendingEntries();
      // 【3】clearOnCommit сбрасывается на false
      reset();
    } 

    private void flushPendingEntries() {
    // 【3】Пройдите по коллекции записей ToAddOnCommit и добавьте записи изкэш в Кэш. Внутри L2
      for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
        delegate.putObject(entry.getKey(), entry.getValue());
      }
      // 【4】Пройдите по коллекции записейMissedInCache, добавьте в Кэш коллекцию записейToAddOnCommit, не содержащую элементов изкэша. Л2
      for (Object entry : entriesMissedInCache) {
        if (!entriesToAddOnCommit.containsKey(entry)) {
          delegate.putObject(entry, null);
        }
      }
    }
}

  • 【1】Перед отправкой транзакции очистите существующие. Грязные данные L2
  • 【2】Сохраните коллекцию записей ToAddOnCommit в Кэш L2.
  • 【3】Пройдите по коллекции записей ToAddOnCommit и добавьте записи изкэш в Кэш. Внутри L2
  • 【4】Просмотр коллекции записейMissedInCache.,ВоляentriesToAddOnCommitнаборНет предмета изкэш, добавьте его в Кэш Л2
Кэш 1 уровняи Кэш Сравнение L2

Чтобы повысить производительность запросов, MyBatis Кэш предоставлен Архитектура L2, разделенная на один уровень кэши Кэш L2。

Самая большая разница между этими двумя уровнями заключается в следующем:

  1. Кэш 1 уровнядавстреча话уровеньиз,Пока этот SqlSession, кэш бесполезен
  2. Кэш L2 работает между сеансами, несколько сеансов могут использовать один и тот же изкэш.
  3. Кэш 1 уровень прост в использовании,Он включен по умолчанию. Кэш L2 необходимо включать вручную,относительно сложный,Есть много вещей, на которые стоит обратить внимание,в противном случае Могут быть скрытые опасности
  4. Кэш L2из key Кэш первого уровня key да Такой жеиз

4. Выполнение оператора

SimpleStatementHandler#update
Язык кода:javascript
копировать
SimpleStatementHandler#update

/**
 * Выполнить операцию обновления
 * 
 * @param statement Оператор SQL из исполняемого объекта
 * @return к Количество строк, на которые влияет операция обновления.
 * @throws SQLException если Выполнить операцию Произошла ошибка при обновлении
 */
@Override
public int update(Statement statement) throws SQLException {
  // Получить оператор SQL
  String sql = boundSql.getSql();
  // Получать Параметры, передаваемые в оператор SQL
  Object parameterObject = boundSql.getParameterObject();
  // Получить генератор ключей
  KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
  // Используется для записи количества затронутых строк.
  int rows;

  // Если генератор ключей даJdbc3KeyGeneratorтип
  if (keyGenerator instanceof Jdbc3KeyGenerator) {
    // Выполнить SQL и вернуть сгенерированные ключи
    statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
    // Получить количество затронутых строк
    rows = statement.getUpdateCount();
    // существуют После выполнения постобработки SQL сгенерируйте ключ из
    keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
  } else if (keyGenerator instanceof SelectKeyGenerator) {
    // Если генератор ключейдаSelectKeyGeneratorтип
    // Выполнить SQL, но не возвращать сгенерированные ключи
    statement.execute(sql);
    // Получить количество затронутых строк
    rows = statement.getUpdateCount();
    // существуют После выполнения постобработки SQL сгенерируйте ключ из
    keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
  } else {
    // Если генератор ключей не поддерживает два вышеуказанных типа
    // Простое выполнение SQL
    statement.execute(sql);
    // Получить количество затронутых строк
    rows = statement.getUpdateCount();
  }
  // Возвращает количество затронутых строк
  return rows;
}

keyGenerator.processAfter

Jdbc3KeyGenerator в качестве примера

Язык кода:javascript
копировать
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  processBatch(ms, stmt, parameter);
}

public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
    // 【1】
  final String[] keyProperties = ms.getKeyProperties();
  if (keyProperties == null || keyProperties.length == 0) {
    return;
  }
  // 【2】
  try (ResultSet rs = stmt.getGeneratedKeys()) {
    final ResultSetMetaData rsmd = rs.getMetaData();
    final Configuration configuration = ms.getConfiguration();
    if (rsmd.getColumnCount() < keyProperties.length) {
      // Error?
    } else {
    // 【3】
      assignKeys(configuration, rs, rsmd, keyProperties, parameter);
    }
  } catch (Exception e) {
    throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
  }
}

Мы можем использовать блок-схему для анализа

SimpleStatementHandler#query
Язык кода:javascript
копировать
/**
 * Выполнить запрос并иметь дело срезультатнабор
 * 
 * @param statement Оператор SQL из инкапсулированного объекта, используемый для выполнения операторов SQL.
 * @param resultHandler Объект обработки набора результатов, используемый для обработки результатов запроса.
 * @return Возвращает список, содержащий результаты запроса.
 * @throws SQLException Если при выполнении оператора SQL возникает ошибка
 * 
 */
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  // Из объекта BoundSql оператор SQL
  String sql = boundSql.getSql();
  // Выполнить оператор SQL
  statement.execute(sql);
  // Обработать и вернуть набор результатов
  return resultSetHandler.handleResultSets(statement);
}

Подвести итог

Приведенный выше даSpringBoot интегрирует код фреймворка Mybatis. Анализ исходного кода Мы исходим из двух аспектов:

  • Во время инициализации Mybatis инкапсулирует MapperMethod слой за слоем.
  • Время выполнения изMapperMethod

Подвести итог: получил следующие знания:

  • Принцип автоматической загрузки SpringBoot
  • Кэш 1 уровняи Кэш L2 из разницы и потокобезопасности
  • генератор первичного ключа
  • SqlSessionInterceptor в режиме прокси
boy illustration
Углубленный анализ переполнения памяти CUDA: OutOfMemoryError: CUDA не хватает памяти. Попыталась выделить 3,21 Ги Б (GPU 0; всего 8,00 Ги Б).
boy illustration
[Решено] ошибка установки conda. Среда решения: не удалось выполнить первоначальное зависание. Повторная попытка с помощью файла (графическое руководство).
boy illustration
Прочитайте нейросетевую модель Трансформера в одной статье
boy illustration
.ART Теплые зимние предложения уже открыты
boy illustration
Сравнительная таблица описания кодов ошибок Amap
boy illustration
Уведомление о последних правилах Points Mall в декабре 2022 года.
boy illustration
Даже новички могут быстро приступить к работе с легким сервером приложений.
boy illustration
Взгляд на RSAC 2024|Защита конфиденциальности в эпоху больших моделей
boy illustration
Вы используете ИИ каждый день и до сих пор не знаете, как ИИ дает обратную связь? Одна статья для понимания реализации в коде Python общих функций потерь генеративных моделей + анализ принципов расчета.
boy illustration
Используйте (внутренний) почтовый ящик для образовательных учреждений, чтобы использовать Microsoft Family Bucket (1T дискового пространства на одном диске и версию Office 365 для образовательных учреждений)
boy illustration
Руководство по началу работы с оперативным проектом (7) Практическое сочетание оперативного письма — оперативного письма на основе интеллектуальной системы вопросов и ответов службы поддержки клиентов
boy illustration
[docker] Версия сервера «Чтение 3» — создайте свою собственную программу чтения веб-текста
boy illustration
Обзор Cloud-init и этапы создания в рамках PVE
boy illustration
Корпоративные пользователи используют пакет регистрационных ресурсов для регистрации ICP для веб-сайта и активации оплаты WeChat H5 (с кодом платежного узла версии API V3)
boy illustration
Подробное объяснение таких показателей производительности с высоким уровнем параллелизма, как QPS, TPS, RT и пропускная способность.
boy illustration
Удачи в конкурсе Python Essay Challenge, станьте первым, кто испытает новую функцию сообщества [Запускать блоки кода онлайн] и выиграйте множество изысканных подарков!
boy illustration
[Техническая посадка травы] Кровавая рвота и отделка позволяют вам необычным образом ощипывать гусиные перья! Не распространяйте информацию! ! !
boy illustration
[Официальное ограниченное по времени мероприятие] Сейчас ноябрь, напишите и получите приз
boy illustration
Прочтите это в одной статье: Учебник для няни по созданию сервера Huanshou Parlu на базе CVM-сервера.
boy illustration
Cloud Native | Что такое CRD (настраиваемые определения ресурсов) в K8s?
boy illustration
Как использовать Cloudflare CDN для настройки узла (CF самостоятельно выбирает IP) Гонконг, Китай/Азия узел/сводка и рекомендации внутреннего высокоскоростного IP-сегмента
boy illustration
Дополнительные правила вознаграждения амбассадоров акции в марте 2023 г.
boy illustration
Можно ли открыть частный сервер Phantom Beast Palu одним щелчком мыши? Супер простой урок для начинающих! (Прилагается метод обновления сервера)
boy illustration
[Играйте с Phantom Beast Palu] Обновите игровой сервер Phantom Beast Pallu одним щелчком мыши
boy illustration
Maotouhu делится: последний доступный внутри страны адрес склада исходного образа Docker 2024 года (обновлено 1 декабря)
boy illustration
Кодирование Base64 в MultipartFile
boy illustration
5 точек расширения SpringBoot, супер практично!
boy illustration
Глубокое понимание сопоставления индексов Elasticsearch.
boy illustration
15 рекомендуемых платформ разработки с нулевым кодом корпоративного уровня. Всегда найдется та, которая вам понравится.
boy illustration
Аннотация EasyExcel позволяет экспортировать с сохранением двух десятичных знаков.