Implementing pagination in MyBatis can be achieved through native SQL clauses, framework-level boundary objects, or dedicated third-party extensions. The following sections outline the implementation details for each approach.
Native LIMIT Clause Implementation
Utilizing the database-native LIMIT directive provides precise control over result sets. This method requires passing offset and page size parameters from the application layer to the data access object.
Interface Definition
public interface DataMapper {
List<UserEntity> fetchPaginatedData(@Param("offset") int start, @Param("pageSize") int limit);
}
XML Configuration Parameter binding is handled directly within the query string. A custom mapping definition is used to align entity properties with specific column names.
<resultMap id="userMapping" type="UserEntity">
<result property="secretKey" column="access_token"/>
</resultMap>
<select id="fetchPaginatedData" resultMap="userMapping">
SELECT * FROM app_users LIMIT #{offset}, #{pageSize}
</select>
Execution Test Direct mapper invocation ensures efficient resource handling. Parameters are encapsulated for clarity.
@Test
void executeLimitPagination() {
try (SqlSession session = MyBatisUtils.openSession()) {
DataMapper dao = session.getMapper(DataMapper.class);
int startingIndex = 5;
int recordsPerPage = 10;
List<UserEntity> results = dao.fetchPaginatedData(startingIndex, recordsPerPage);
results.forEach(System.out::println);
}
}
In-Memory RowBounds Approach
Unlike SQL-based slicing, RowBounds operates at the JDBC result set level. The underlying query retrieves all records, and the framework applies offset and count constraints in memory before returning them to the caller. This approach bypasses the traditional mapper proxy mechanism in favor of direct SqlSession methods.
Interface Definition
public interface BoundaryMapper {
List<UserEntity> loadAllRecords();
}
XML Configuration Since boundary constraints are handled by the session utility, the statement returns the complete dataset.
<select id="loadAllRecords" resultMap="userMapping">
SELECT * FROM app_users
</select>
Execution Test
The selectList method supports an overloaded signature that accepts a RowBounds instance. Initialization requires defining the starting position and the maximum number of rows to return.
@Test
void executeRowBoundsPagination() {
try (SqlSession session = MyBatisUtils.openSession()) {
RowBounds boundaries = new RowBounds(0, 5);
String statementId = "com.example.BoundaryMapper.loadAllRecords";
List<UserEntity> subset = session.selectList(statementId, null, boundaries);
subset.forEach(System.out::println);
}
}
The RowBounds class exposes constants for default offsets and unbounded limits, along with standard getter methods to retrieve the configured slice range.
Third-Party Extension Itnegration
Manual configuration of boundary logic and frequent updates to DAO layers introduce maintenance overhead. Production environments tyipcally leverage established pagination libraries, such as PageHelper. These interceptors dynamically modify the execution plan at runtime, automatically appending dialect-specific pagination syntax to standard select statements without altering the original business logic. Configuration follows library-specific documentation, involving interceptor registration and environment initialization.