Как MyBatis реализует потоковый запрос?

задняя часть MyBatis
Как MyBatis реализует потоковый запрос?

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

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

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

Интерфейс потокового запроса MyBatis

MyBatis предоставляетorg.apache.ibatis.cursor.CursorКласс интерфейса используется для потокового запроса, этот интерфейс наследуетjava.io.Closeableа такжеjava.lang.Iterableинтерфейс, видно что:

  • Курсор закрывается. Фактически, когда Cursor закрывается, соединение с базой данных также закрывается;
  • Курсор можно перемещать.

Кроме того, Cursor также предоставляет три метода:

  • isOpen(): Используется для определения того, открыт ли объект Cursor перед выборкой данных. Курсор может получать данные только тогда, когда он открыт;
  • isConsumed(): используется для оценки того, были ли получены все результаты запроса;
  • getCurrentIndex(): Возвращает количество полученных фрагментов данных.

Поскольку Cursor реализует интерфейс итератора, очень просто получить данные из Cursor при фактическом использовании:

try(Cursor cursor = mapper.querySomeData()) {
    cursor.forEach(rowObject -> {
        // ...
    });
}

Курсор можно закрыть автоматически с помощью метода try-resource.

Но процесс создания курсора не прост

Возьмем практический пример. Вот класс Mapper:

@Mapper
public interface FooMapper {
    @Select("select * from foo limit #{limit}")
    Cursor<Foo> scan(@Param("limit") int limit);
}

Метод scan() — это очень простой запрос. Когда мы определяем этот метод, мы указываем, что возвращаемое значение имеет тип Cursor, и MyBatis понимает, что этот метод запроса является потоковым запросом.

Затем мы пишем еще один метод контроллера SpringMVC для вызова Mapper (не относящийся к делу код был опущен):

@GetMapping("foo/scan/0/{limit}")
public void scanFoo0(@PathVariable("limit") int limit) throws Exception {
    try (Cursor<Foo> cursor = fooMapper.scan(limit)) {  // 1
        cursor.forEach(foo -> {});                      // 2
    }
}

Предположим, что fooMapper включен @Autowired. Примечание 1 — получить объект Cursor и убедиться, что его можно закрыть последним; 2 — извлечь данные из курсора.

Приведенный выше код выглядит нормально, но выполняетсяscanFoo0(int)сообщит об ошибке:

java.lang.IllegalStateException: A Cursor is already closed.

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

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

Вариант 1: SqlSessionFactory

Мы можем использовать SqlSessionFactory, чтобы вручную открыть соединение с базой данных и изменить метод Controller следующим образом:

@GetMapping("foo/scan/1/{limit}")
public void scanFoo1(@PathVariable("limit") int limit) throws Exception {
    try (
        SqlSession sqlSession = sqlSessionFactory.openSession();  // 1
        Cursor<Foo> cursor = 
              sqlSession.getMapper(FooMapper.class).scan(limit)   // 2
    ) {
        cursor.forEach(foo -> { });
    }
}

В приведенном выше коде в 1 месте мы открываем SqlSession (на самом деле представляет собой соединение с базой данных) и гарантируем, что его можно будет в конечном итоге закрыть; во 2 местах мы используем SqlSession для получения объекта Mapper. Это гарантирует, что результирующий объект Cursor открыт.

Вариант 2: Шаблон транзакции

В Spring мы можем использовать TransactionTemplate для выполнения транзакции базы данных, и соединение с базой данных также открыто во время этого процесса. код показывает, как показано ниже:

@GetMapping("foo/scan/2/{limit}")
public void scanFoo2(@PathVariable("limit") int limit) throws Exception {
    TransactionTemplate transactionTemplate = 
            new TransactionTemplate(transactionManager);  // 1
    transactionTemplate.execute(status -> {               // 2
        try (Cursor<Foo> cursor = fooMapper.scan(limit)) {
            cursor.forEach(foo -> { });
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    });
}

В приведенном выше коде мы создаем объект TransactionTemplate в 1 месте (нет необходимости объяснять, как здесь появился transactionManager, в этой статье предполагается, что читатель знаком с использованием транзакций базы данных Spring), во 2 местах выполняются транзакции базы данных, а в содержимое транзакций базы данных Это потоковый запрос, который вызывает объект Mapper. Обратите внимание, что объект Mapper здесь не нужно создавать с помощью SqlSession.

Вариант 3: аннотация @Transactional

Это по сути то же самое, что и второе решение, код выглядит следующим образом:

@GetMapping("foo/scan/3/{limit}")
@Transactional
public void scanFoo3(@PathVariable("limit") int limit) throws Exception {
    try (Cursor<Foo> cursor = fooMapper.scan(limit)) {
        cursor.forEach(foo -> { });
    }
}

Это просто дополнение к оригинальному методу.@Transactionalаннотация. Это решение выглядит наиболее лаконичным, но обратите внимание на ямки, используемые аннотациями в фреймворке Spring:Вступает в силу только при внешнем вызове. Вызов этого метода в текущем классе по-прежнему будет сообщать об ошибке.

Выше приведены три способа реализации потокового запроса MyBatis.

Первый адрес:Как MyBatis реализует потоковый запрос?

Обратите внимание на технический номер:Официальный аккаунт: Code Farm Architecture