Skip to main content
Hint

This article is generated by AI translation.

Custom type handlers

Write a custom handler when the built-in ones do not fit your needs.

Example: write a String as Timestamp
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.

Use a custom handler on an argument
// 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 MyDateTypeHandler handles the argument.
Use a custom handler in object mapping
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);
Use a custom handler in entity mapping XML
<!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>
Use a custom handler in resultMap XML
<!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.

Replace the default StringTypeHandler
// 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);
info

TypeHandlerRegistry.DEFAULT is the global registry; create a new one if you need custom behavior.

Binding

Bind to a Java type

Query example
select * from users where name = #{name, javaType=java.lang.String}
Register in code
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}

TypeHandlerRegistry typeRegistry = ...
typeRegistry.register(String.class, new MyStringTypeHandler());
Register via annotation
@MappedJavaTypes(String.class)
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}

TypeHandlerRegistry typeRegistry = ...
typeRegistry.registerHandler(MyStringTypeHandler.class, new MyStringTypeHandler());

Bind to a JDBC type

Query example
select * from users where name = #{name, jdbcType=varchar}
Register in code
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}

TypeHandlerRegistry typeRegistry = ...
typeRegistry.register(Types.VARCHAR, new MyStringTypeHandler());
Register via annotation
@MappedJdbcTypes(String.class)
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}

TypeHandlerRegistry typeRegistry = ...
typeRegistry.registerHandler(MyStringTypeHandler.class, new MyStringTypeHandler());

Cross bind types

Query example
select * from users where name = #{name, jdbcType=nvarchar, javaType=java.lang.String}
Register in code
public class MyStringTypeHandler extends AbstractTypeHandler<String> {
...
}

TypeHandlerRegistry typeRegistry = ...
typeRegistry.register(Types.NVARCHAR, String.class, new MyStringTypeHandler());
Register via annotation
@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 EnumTypeHandler uses this).
Usage
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 for UserTypeEnum, leading to incorrect behavior.

Annotate the handler with @NoCache to bypass the cache:

@NoCache
public class MyTypeHandler extends AbstractTypeHandler<Object> {
public MyTypeHandler(Class<?> argType) {
...
}
}
info

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