Master MyBatis Streaming Queries: Keep DB Connections Open Efficiently
This article explains MyBatis streaming queries using the Cursor interface, demonstrates common pitfalls such as closed connections, and provides three practical solutions—including SqlSessionFactory, TransactionTemplate, and @Transactional—to reliably process large result sets with minimal memory usage.
MyBatis streaming query returns an iterator (Cursor) instead of a collection, reducing memory usage when handling large result sets.
The Cursor interface extends java.io.Closeable and java.lang.Iterable, providing methods isOpen(), isConsumed(), and getCurrentIndex().
Example Mapper:
@Mapper
public interface FooMapper {
@Select("select * from foo limit #{limit}")
Cursor<Foo> scan(@Param("limit") int limit);
}Controller usage:
@GetMapping("foo/scan/0/{limit}")
public void scanFoo0(@PathVariable("limit") int limit) throws Exception {
try (Cursor<Foo> cursor = fooMapper.scan(limit)) {
cursor.forEach(foo -> {});
}
}This code fails with java.lang.IllegalStateException: A Cursor is already closed. because the database connection is closed after the mapper method returns.
To keep the connection open, three approaches are presented:
Solution 1: SqlSessionFactory
@GetMapping("foo/scan/1/{limit}")
public void scanFoo1(@PathVariable("limit") int limit) throws Exception {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
Cursor<Foo> cursor = sqlSession.getMapper(FooMapper.class).scan(limit);
cursor.forEach(foo -> {});
}
}Solution 2: TransactionTemplate
@GetMapping("foo/scan/2/{limit}")
public void scanFoo2(@PathVariable("limit") int limit) throws Exception {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(status -> {
try (Cursor<Foo> cursor = fooMapper.scan(limit)) {
cursor.forEach(foo -> {});
} catch (IOException e) {
e.printStackTrace();
}
return null;
});
}Solution 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 -> {});
}
}Note that @Transactional only works when the method is called from outside the class.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
