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.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Master MyBatis Streaming Queries: Keep DB Connections Open Efficiently

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavadatabasespringMyBatisCursorStreaming Query
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.