How to Implement Efficient MyBatis Streaming Queries in Spring Boot

This article explains what streaming queries are, why MyBatis's Cursor requires an open database connection, and presents three practical solutions—using SqlSessionFactory, TransactionTemplate, or @Transactional—to correctly implement MyBatis streaming queries in Spring applications while avoiding common pitfalls.

Architecture Digest
Architecture Digest
Architecture Digest
How to Implement Efficient MyBatis Streaming Queries in Spring Boot

Streaming query returns an iterator instead of a full collection, reducing memory usage.

When the database connection is closed after the mapper method, the MyBatis Cursor also closes, causing errors; therefore the connection must stay open during iteration.

MyBatis streaming query interface

MyBatis provides org.apache.ibatis.cursor.Cursor, which extends java.io.Closeable and java.lang.Iterable. The cursor is closable and iterable and offers three methods: isOpen() – checks if the cursor is open. isConsumed() – checks if all results have been read. getCurrentIndex() – returns the number of rows already fetched.

Typical usage:

cursor.forEach(rowObject -> {...});

Problem when building a Cursor

A mapper method returning Cursor<Foo> works, but the connection is closed after the method returns, leading to

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

Solutions to keep the connection open

Solution 1: SqlSessionFactory

Manually open a SqlSession and obtain the mapper from it.

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

Solution 2: TransactionTemplate

Execute the query inside a Spring TransactionTemplate so the connection stays 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

Add @Transactional to the controller method; the transaction keeps the connection open, but the annotation only works when the method is invoked from another bean.

@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 approaches enable proper MyBatis streaming queries in Spring applications.

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.

JavaSpring BootMyBatisCursorStreaming QueryDatabase Connection
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

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.