8.2 Custom Type Handlers
When the type handlers provided by dbVisitor do not meet your needs, you can create your own custom type handler.
Extend AbstractTypeHandler<T> and implement the 4 abstract methods:
package net.demos.dto;
public class MyDateTypeHandler extends AbstractTypeHandler<String> {
public void setNonNullParameter(PreparedStatement ps, int i,
String parameter, Integer jdbcType) throws SQLException {
try {
Date date = new SimpleDateFormat("yyyy-MM-dd").parse(parameter);
ps.setTimestamp(i, new Timestamp(date.getTime()));
} catch (ParseException e) {
throw new SQLException(e);
}
}
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return fmtDate(rs.getTimestamp(columnName));
}
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return fmtDate(rs.getTimestamp(columnIndex));
}
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return fmtDate(cs.getTimestamp(columnIndex));
}
private String fmtDate(Timestamp sqlTimestamp) {
if (sqlTimestamp != null) {
return new SimpleDateFormat("yyyy-MM-dd").format(new Date(sqlTimestamp.getTime()));
}
return null;
}
}
Explicit Reference
Explicit reference is the most common usage—you specify the type handler directly in SQL or code.
String time = "2019-10-11";
jdbc.queryForList("select * from users where create_time = #{arg0, typeHandler=net.demos.dto.MyDateTypeHandler}", time);
- Uses
#{...}syntax with positional naming to pass arguments. - Uses the typeHandler parameter option to set MyDateTypeHandler for reading and writing the parameter.
public class User {
@Column(typeHandler = MyDateTypeHandler.class)
private String myTime;
// getters and setters omitted
}
// Query
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 namespace="net.demos.dto">
<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
Implicit reference is used to replace the default type handlers provided by dbVisitor, or to add default support for a new type. This is achieved through registration, without needing to explicitly specify the handler at each usage point.
// Custom handler
@MappedJavaTypes(String.class)
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}
// Register via registerHandler, which automatically reads the annotation and binds to String.class
TypeHandlerRegistry.DEFAULT.registerHandler(MyStringTypeHandler.class, new MyStringTypeHandler());
// Now all String type reading/writing in dbVisitor will use MyStringTypeHandler
// User class no longer needs to specify typeHandler explicitly
jdbc.queryForList("select * from user_table where name = ?", arg, User.class);
TypeHandlerRegistry.DEFAULT is the global type handler registry. Create a new instance if you need custom behavior.
Type 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(Types.VARCHAR)
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}
TypeHandlerRegistry typeRegistry = ...;
typeRegistry.registerHandler(MyStringTypeHandler.class, new MyStringTypeHandler());
Cross-Type Binding
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
dbVisitor allows custom type handlers to have a constructor that takes a Class parameter.
For example, when a query parameter is an enum or a result set maps to an enum field, the type handler needs to know how to translate the field value into an enum object.
- Handler constructor arguments let the type handler know the concrete Java type being operated on (dbVisitor's built-in
EnumTypeHandleruses this mechanism).
public class MyTypeHandler extends AbstractTypeHandler<Object> {
public MyTypeHandler(Class<?> argType) {
...
}
}
@NoCache Annotation
The registry TypeHandlerRegistry has a caching mechanism to speed up TypeHandler creation and retrieval.
When a type handler uses a constructor argument, the cache may hit an already-created typeHandler while ignoring the same typeHandler with different arguments. 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 the same type handler MyTypeHandler for different Java enum types.
- Without
@NoCache, the second query would have dbVisitor treat the argument as UserTypeEnum, causing confusion.
Use @NoCache to avoid cache interference:
@NoCache
public class MyTypeHandler extends AbstractTypeHandler<Object> {
public MyTypeHandler(Class<?> argType) {
...
}
}
Any operation that manually registers a TypeHandler into TypeHandlerRegistry is not affected by @NoCache.
For example, the following registrations will successfully and permanently bind the MyTypeHandler instance to the corresponding type combination regardless of @NoCache:
typeRegistry.registerHandler(MyTypeHandler.class, new MyTypeHandler(String.class));
typeRegistry.register(String.class, new MyTypeHandler(String.class));
typeRegistry.register(Types.NVARCHAR, new MyTypeHandler(String.class));
typeRegistry.register(Types.NVARCHAR, String.class, new MyTypeHandler(String.class));
@NoCache only takes effect when the registry passively creates a TypeHandler via the createTypeHandler method.