Введение. Под потоковым запросом понимается возврат итератора вместо коллекции после успешного выполнения запроса, и приложение каждый раз извлекает результат запроса из итератора. Преимущество потоковых запросов заключается в возможности сократить использование памяти.
Если нет потокового запроса, когда мы хотим получить 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