Databases 6 min read

Mastering MyBatis Streaming Queries: Keep Connections Open and Avoid Cursor Errors

Streaming queries return an iterator instead of a full result set, reducing memory usage, but require the database connection to stay open; this guide explains MyBatis’s Cursor interface, its methods, common pitfalls like premature closure, and three practical solutions using SqlSessionFactory, TransactionTemplate, or @Transactional to ensure reliable streaming.

Java Interview Crash Guide
Java Interview Crash Guide
Java Interview Crash Guide
Mastering MyBatis Streaming Queries: Keep Connections Open and Avoid Cursor Errors

Streaming queries return an iterator rather than a full collection, allowing lower memory consumption; without them, large result sets require pagination, which can be inefficient depending on table design.

The MyBatis framework provides the org.apache.ibatis.cursor.Cursor interface for streaming queries, which extends java.io.Closeable and java.lang.Iterable. A Cursor is closeable (closing it also closes the DB connection) and iterable.

Cursor offers three useful methods:

isOpen() : checks if the Cursor is open before fetching data.

isConsumed() : determines whether all results have been retrieved.

getCurrentIndex() : returns the number of rows already fetched.

Using a Cursor is straightforward:

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

However, the Cursor’s underlying connection remains open, so the application must close it after use. Building a Cursor can be tricky, and a common error is “Cursor is already closed” because the Mapper method closes the connection after execution.

To keep the connection open, three approaches are presented:

Solution 1: SqlSessionFactory

Manually open a SqlSession (which holds the connection) and obtain the Cursor from it:

@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

Execute the streaming query within a Spring transaction, which keeps the connection open:

@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 Annotation

Annotate the method with @Transactional to run it in a transaction, ensuring the connection stays open. Note that the annotation only works on external calls:

@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 -> { });
    }
}

These three methods enable reliable MyBatis streaming queries by maintaining an open database connection throughout data retrieval.

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.

JavadatabaseMyBatisCursorStreaming Queries
Java Interview Crash Guide
Written by

Java Interview Crash Guide

Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.

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.