Implementing Streaming Reads with MyBatis for Large-Scale Java Report Export

This article explains how to overcome export failures caused by large data volumes in a legacy Java system by switching from default full-result JDBC reads to a forward‑only streaming approach using MyBatis, detailing environment setup, configuration changes, and complete code examples for controller, service, DAO, and mapper layers.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Implementing Streaming Reads with MyBatis for Large-Scale Java Report Export

When the company's legacy reporting module failed to export datasets larger than ten thousand rows, analysis revealed that the default JDBC fetch retrieved all rows at once, causing excessive memory usage and GC pressure. The solution was to adopt a forward‑only streaming read, which fetches rows incrementally.

JDBC three read modes:

1. Full fetch (default) – retrieves the entire result set in one go.

2. Streaming – fetches one row at a time.

3. Cursor – fetches multiple rows per round.

MyBatis uses the first mode by default.

Development environment: JDK 1.8, IntelliJ IDEA 2018, MyBatis 3, Spring MVC, Spring 4.

Implementation steps: The project adopts a layered architecture (controller → service → DAO) and modifies the service method to accept a ResultHandler callback, while the DAO method returns void. The mapper XML is configured with resultSetType="FORWARD_ONLY" and fetchSize="-2147483648" to enable streaming.

Controller layer:

@RequestMapping("/export")
public void export(Vo vo, String props, HttpServletResponse response) {
    // ...
    list = ossVipCustomService.selectForwardOnly(vo, Order.build());
    // ...
}

Service layer (core logic):

public List<Bo> selectForwardOnly(Vo vo, Order order) {
    final List<Bo> list = new ArrayList<>();
    mapper.selectForwardOnly(vo, order, new ResultHandler<Bo>() {
        @Override
        public void handleResult(ResultContext<? extends Bo> resultContext) {
            // callback processing
            list.add(resultContext.getResultObject());
        }
    });
    return list;
}

DAO interface:

/**
 * Streaming read of data
 */
void selectForwardOnly(@Param("record") Vo vo,
                       @Param("order") Order order,
                       ResultHandler<Bo> handler);

Mapper XML configuration:

<select id="selectForwardOnly"
        parameterType="com.*.Vo" resultMap="GetListBo"
        resultSetType="FORWARD_ONLY" fetchSize="-2147483648">
    SELECT * FROM customer
</select>

The author notes that returning void from the DAO is intentional because the result is processed in the callback, not passed back to the service layer.

Personal reflections mention extensive Googling, adjustments to MySQL connection parameters and MyBatis settings, and the realization that the DAO should not expose a return value.

Additional resources and promotional links are included but are not part of the technical solution.

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.

JavasqlStreamingMyBatisJDBC
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.