This article is generated by AI translation.
Custom type handlers
Write a custom handler when the built-in ones do not fit your needs.
package net.demos.dto;
public class MyDateTypeHandler extends AbstractTypeHandler<String> {
public void setNonNullParameter(PreparedStatement ps, int i, String argument, Integer jdbcType) {
try {
Date date = new SimpleDateFormat("yyyy-MM-dd").parse(argument);
ps.setTimestamp(i, new Timestamp(date.getTime()));
} catch (Exception e) {
throw new SQLException(e);
}
}
public String getNullableResult(ResultSet rs, String columnName) {
return fmtDate(rs.getTimestamp(columnName));
}
public String getNullableResult(ResultSet rs, int columnIndex) {
return fmtDate(rs.getTimestamp(columnIndex));
}
public String getNullableResult(CallableStatement cs, int columnIndex) {
return fmtDate(cs.getTimestamp(columnIndex));
}
private String fmtDate(Timestamp sqlTimestamp) {
if (sqlTimestamp != null) {
Date date = new Date(sqlTimestamp.getTime());
return new SimpleDateFormat("yyyy-MM-dd").format(date);
}
return null;
}
}
Explicit reference
The common approach is to explicitly declare the handler in SQL or mapping code.
// Query argument
String time = "2019-10-11";
jdbc.queryForList("select * from users where create_time = #{arg0, typeHandler=net.demos.dto.MyDateTypeHandler}", time);
- Uses
#{...}with positional Naming arguments. - Uses the typeHandler option so
MyDateTypeHandlerhandles the argument.
public class User {
@Column(typeHandler = MyDateTypeHandler.class)
private String myTime;
// getters and setters omitted
}
// Query argument
jdbc.queryForList("select * from users where id > ?", 2, User.class);
<!DOCTYPE mapper PUBLIC "-//dbvisitor.net//DTD Mapper 1.0//EN"
"https://www.dbvisitor.net/schema/dbvisitor-mapper.dtd">
<mapper>
<entity table="users" type="net.demos.dto.User">
...
<mapping column="my_time" property="myTime" typeHandler="net.demos.dto.MyDateTypeHandler"/>
...
</entity>
</mapper>
<!DOCTYPE mapper PUBLIC "-//dbvisitor.net//DTD Mapper 1.0//EN"
"https://www.dbvisitor.net/schema/dbvisitor-mapper.dtd">
<mapper namespace="net.demos.dto">
<resultMap id="user_resultMap" type="net.demos.dto.User">
...
<result column="my_time" property="myTime" typeHandler="net.demos.dto.MyDateTypeHandler"/>
...
</resultMap>
</mapper>
Implicit reference
Register a handler as the default for a Java type or JDBC type to replace a built-in or add support for a new type.
// Custom handler
@MappedJavaTypes(String.class)
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}
// Register handler
TypeHandlerRegistry.DEFAULT.registerHandler(MyStringTypeHandler.class, new MyStringTypeHandler());
// All string handling now uses MyStringTypeHandler across dbVisitor,
// so User no longer needs to specify typeHandler on fields.
jdbc.queryForList("select * from user_table where name = ?", arg, User.class);
TypeHandlerRegistry.DEFAULT is the global registry; create a new one if you need custom behavior.
Binding
Bind to a Java type
select * from users where name = #{name, javaType=java.lang.String}
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}
TypeHandlerRegistry typeRegistry = ...
typeRegistry.register(String.class, new MyStringTypeHandler());
@MappedJavaTypes(String.class)
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}
TypeHandlerRegistry typeRegistry = ...
typeRegistry.registerHandler(MyStringTypeHandler.class, new MyStringTypeHandler());
Bind to a JDBC type
select * from users where name = #{name, jdbcType=varchar}
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}
TypeHandlerRegistry typeRegistry = ...
typeRegistry.register(Types.VARCHAR, new MyStringTypeHandler());
@MappedJdbcTypes(String.class)
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}
TypeHandlerRegistry typeRegistry = ...
typeRegistry.registerHandler(MyStringTypeHandler.class, new MyStringTypeHandler());
Cross bind types
select * from users where name = #{name, jdbcType=nvarchar, javaType=java.lang.String}
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}
TypeHandlerRegistry typeRegistry = ...
typeRegistry.register(Types.NVARCHAR, String.class, new MyStringTypeHandler());
@MappedCrossTypes(javaType = String.class, jdbcType = Types.NVARCHAR)
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}
TypeHandlerRegistry typeRegistry = ...
typeRegistry.registerHandler(MyStringTypeHandler.class, new MyStringTypeHandler());
Handler constructor arguments
Custom handlers may declare a constructor that takes a Class argument.
For example, when handling enums the handler needs to know which enum class to map to.
- This constructor argument lets the handler know the concrete Java type (the built-in
EnumTypeHandleruses this).
public class MyTypeHandler extends AbstractTypeHandler<Object> {
public MyTypeHandler(Class<?> argType) {
...
}
}
@NoCache
TypeHandlerRegistry caches handler instances to speed up lookup.
When a handler uses the Class constructor, the cache may incorrectly reuse an instance for the wrong Java type. For example:
select * from users
where user_type = #{arg0, javaType= net.demos.dto.UserTypeEnum, ➊
typeHandler=net.demos.dto.MyTypeHandler}
select * from users
where auth_type = #{arg0, javaType= net.demos.dto.AuthTypeEnum, ➋
typeHandler=net.demos.dto.MyTypeHandler}
- Both queries use
MyTypeHandler, but for different enum types. - Without
@NoCache, the second query might reuse the handler created forUserTypeEnum, leading to incorrect behavior.
Annotate the handler with @NoCache to bypass the cache:
@NoCache
public class MyTypeHandler extends AbstractTypeHandler<Object> {
public MyTypeHandler(Class<?> argType) {
...
}
}
@NoCache does not affect handlers that you register manually in TypeHandlerRegistry.
For example, these registrations work regardless of @NoCache:
typeRegistry.registerHandler(MyTypeHandler.class, new MyTypeHandler());
typeRegistry.register(String.class, new MyTypeHandler());
typeRegistry.register(Types.NVARCHAR, new MyTypeHandler());
typeRegistry.register(Types.NVARCHAR, String.class, new MyTypeHandler());
@NoCache only applies when the registry passively creates a handler via createTypeHandler.