Type handlers in MyBatis bridge the gap between JDBC types and Java objects during parameter binding and result mapping. While MyBatis providse built-in handlers for common types, custom handlers enable precise control over serialization and deserialization logic—especially useful when persisting non-standard representations (e.g., storing Date as milliseconds in a BIGINT column).
Implementing a Custom Type Handler
To define a custom handler:
- Create a class extending
BaseTypeHandler<T>, whereTis the target Java type. - Override the four abstract methods:
setNonNullParameter(for outbound parameter binding) and three variants ofgetNullableResult(for inbound result extraction). - Register the handler in MyBatis’ configuration using the
<typeHandlers>section. - Verify behavior via integration tests that exercise both insert and select operations.
Below is a refactored implementation that maps java.time.Instant to a BIGINT column representing epoch milliseconds:
public class InstantMillisHandler extends BaseTypeHandler<Instant> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Instant instant, JdbcType jdbcType)
throws SQLException {
ps.setLong(i, instant.toEpochMilli());
}
@Override
public Instant getNullableResult(ResultSet rs, String columnName) throws SQLException {
long millis = rs.getLong(columnName);
return rs.wasNull() ? null : Instant.ofEpochMilli(millis);
}
@Override
public Instant getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
long millis = rs.getLong(columnIndex);
return rs.wasNull() ? null : Instant.ofEpochMilli(millis);
}
@Override
public Instant getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
long millis = cs.getLong(columnIndex);
return cs.wasNull() ? null : Instant.ofEpochMilli(millis);
}
}
Registration in mybatis-config.xml:
<typeHandlers>
<typeHandler handler="com.example.handlers.InstantMillisHandler"
javaType="java.time.Instant"
jdbcType="BIGINT"/>
</typeHandlers>
Integrating Pagination with PageHelper
The PageHelper plugin simplifies server-side pgaination by transparently rewriting SQL queries and injecting LIMIT/OFFSET clauses (or equivalent dialect-specific constructs). It operates as a MyBatis interceptor and requires minimal setup.
Add dependencies (Maven):
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>4.7</version>
</dependency>
Configure the interceptor in MyBatis’ main configuration:
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- Optional: explicitly declare dialect if auto-detection fails -->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
Use it programmatically before executing a mapped statement:
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// Trigger pagination: page 1, 5 items per page
PageHelper.startPage(1, 5);
List<User> pagedUsers = mapper.selectAll();
// PageInfo provides metadata (total count, page numbers, etc.)
PageInfo<User> pageInfo = new PageInfo<>(pagedUsers);
System.out.println("Total: " + pageInfo.getTotal());
}
A complete MyBatis configuration file incorporating both features looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<properties resource="jdbc.properties"/>
<typeAliases>
<typeAlias type="com.example.domain.User" alias="User"/>
</typeAliases>
<typeHandlers>
<typeHandler handler="com.example.handlers.InstantMillisHandler"
javaType="java.time.Instant"
jdbcType="BIGINT"/>
</typeHandlers>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>