typeHandlers 类型处理器配置
映射器Mapper.xml文件 我们还没开始介绍,如果你是首次阅读该文档,你可能需要先跳过这里,学会 Mapper 再过会看。
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。
提示 从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API) 。
类型处理器 | Java 类型 | JDBC 类型 |
---|---|---|
BooleanTypeHandler |
java.lang.Boolean , boolean |
数据库兼容的 BOOLEAN |
ByteTypeHandler |
java.lang.Byte , byte |
数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler |
java.lang.Short , short |
数据库兼容的 NUMERIC 或 SHORT INTEGER |
IntegerTypeHandler |
java.lang.Integer , int |
数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler |
java.lang.Long , long |
数据库兼容的 NUMERIC 或 LONG INTEGER |
FloatTypeHandler |
java.lang.Float , float |
数据库兼容的 NUMERIC 或 FLOAT |
DoubleTypeHandler |
java.lang.Double , double |
数据库兼容的 NUMERIC 或 DOUBLE |
BigDecimalTypeHandler |
java.math.BigDecimal |
数据库兼容的 NUMERIC 或 DECIMAL |
StringTypeHandler |
java.lang.String |
CHAR , VARCHAR |
ClobReaderTypeHandler |
java.io.Reader |
- |
ClobTypeHandler |
java.lang.String |
CLOB , LONGVARCHAR |
NStringTypeHandler |
java.lang.String |
NVARCHAR , NCHAR |
NClobTypeHandler |
java.lang.String |
NCLOB |
BlobInputStreamTypeHandler |
java.io.InputStream |
- |
ByteArrayTypeHandler |
byte[] |
数据库兼容的字节流类型 |
BlobTypeHandler |
byte[] |
BLOB , LONGVARBINARY |
DateTypeHandler |
java.util.Date |
TIMESTAMP |
DateOnlyTypeHandler |
java.util.Date |
DATE |
TimeOnlyTypeHandler |
java.util.Date |
TIME |
SqlTimestampTypeHandler |
java.sql.Timestamp |
TIMESTAMP |
SqlDateTypeHandler |
java.sql.Date |
DATE |
SqlTimeTypeHandler |
java.sql.Time |
TIME |
ObjectTypeHandler |
Any | OTHER 或未指定类型 |
EnumTypeHandler |
Enumeration Type | VARCHAR-任何兼容的字符串类型,存储枚举的名称(而不是索引) |
EnumOrdinalTypeHandler |
Enumeration Type | 任何兼容的 NUMERIC 或 DOUBLE 类型,存储枚举的索引(而不是名称)。 |
InstantTypeHandler |
java.time.Instant |
TIMESTAMP |
LocalDateTimeTypeHandler |
java.time.LocalDateTime |
TIMESTAMP |
LocalDateTypeHandler |
java.time.LocalDate |
DATE |
LocalTimeTypeHandler |
java.time.LocalTime |
TIME |
OffsetDateTimeTypeHandler |
java.time.OffsetDateTime |
TIMESTAMP |
OffsetTimeTypeHandler |
java.time.OffsetTime |
TIME |
ZonedDateTimeTypeHandler |
java.time.ZonedDateTime |
TIMESTAMP |
YearTypeHandler |
java.time.Year |
INTEGER |
MonthTypeHandler |
java.time.Month |
INTEGER |
YearMonthTypeHandler |
java.time.YearMonth |
VARCHAR or LONGVARCHAR |
JapaneseDateTypeHandler |
java.time.chrono.JapaneseDate |
DATE |
你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler
接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler
, 然后可以选择性地将它映射到一个 JDBC 类型。比如:
// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
使用这个的类型处理器将会覆盖已经存在的处理 Java 的 String 类型属性和 VARCHAR 参数及结果的类型处理器。 要注意 MyBatis 不会窥探数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明那是 VARCHAR 类型的字段, 以使其能够绑定到正确的类型处理器上。 这是因为:MyBatis 直到语句被执行才清楚数据类型。
通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:
- 在类型处理器的配置元素(typeHandler element)上增加一个
javaType
属性(比如:javaType="String"
); - 在类型处理器的类上(TypeHandler class)增加一个
@MappedTypes
注解来指定与其关联的 Java 类型列表。 如果在javaType
属性中也同时指定,则注解方式将被忽略。
可以通过两种方式来指定被关联的 JDBC 类型:
- 在类型处理器的配置元素上增加一个
jdbcType
属性(比如:jdbcType="VARCHAR"
); - 在类型处理器的类上(TypeHandler class)增加一个
@MappedJdbcTypes
注解来指定与其关联的 JDBC 类型列表。 如果在jdbcType
属性中也同时指定,则注解方式将被忽略。
当决定在ResultMap
中使用某一TypeHandler时,此时java类型是已知的(从结果类型中获得),但是JDBC类型是未知的。 因此Mybatis使用javaType=[TheJavaType], jdbcType=null
的组合来选择一个TypeHandler。 这意味着使用@MappedJdbcTypes
注解可以限制TypeHandler的范围,同时除非显式的设置,否则TypeHandler在ResultMap
中将是无效的。 如果希望在ResultMap
中使用TypeHandler,那么设置@MappedJdbcTypes
注解的includeNullJdbcType=true
即可。 然而从Mybatis 3.4.0开始,如果只有一个注册的TypeHandler来处理Java类型,那么它将是ResultMap
使用Java类型时的默认值(即使没有includeNullJdbcType=true
)。
最后,可以让 MyBatis 为你查找类型处理器:
<!-- mybatis-config.xml -->
<typeHandlers>
<package name="org.mybatis.example"/>
</typeHandlers>
注意在使用自动检索(autodiscovery)功能的时候,只能通过注解方式来指定 JDBC 的类型。
你可以创建一个能够处理多个类的泛型类型处理器。为了使用泛型类型处理器, 需要增加一个接受该类的 class 作为参数的构造器,这样在构造一个类型处理器的时候 MyBatis 就会传入一个具体的类。
//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {
private Class<E> type;
public GenericTypeHandler(Class<E> type) {
if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
this.type = type;
}
...
EnumTypeHandler
和 EnumOrdinalTypeHandler
都是泛型类型处理器(generic TypeHandlers), 我们将会在接下来的部分详细探讨。
处理枚举类型
如果想使用mybatis自带的枚举类处理,有2种方式,一个是EnumTypeHandler,一个是EnumOrdinalTypeHandler。 区别如下:
EnumTypeHandler直接存储name值。它是mybatis默认的枚举类型转换器。 EnumOrdinalTypeHandler存储enum类里的序号值,此时数据库表字段一般对应用smallint/int类型的处理。
使用的时侯很简单,在insert或者update语句块,或者resultMap字段等地方指定相应的typeHandler即可。
<insert id="insertUser" parameterType="User">
insert into user(id,userName,status)
values(#{id}, #{userName},#{status, typeHandler=org.apache.ibatis.type.EnumOrdinalTypeHandler})
</insert>
或
<resultMap id="BaseResultMap" type="User">
<id column="id" property="userId" jdbcType="INTEGER" />
<result column="status" property="status" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
</resultMap>
不过大多数情况下,想存储的并不是枚举的顺序号,而是存储自定义的id值,这时我们就需要自己写enum的处理类。
若程序中原来有一个枚举类,name是长沙、株洲、湘潭三地,value采用身份证上的地区编号。
public enum CityTest {
ChangSha(4301),Zhuzhou(4302),Xiangtan(4303);
int value;
private CityTest(int value){
this.value=value;
}
public int getValue() {
return this.value;
}
/*方法Value2CityTest是为了typeHandler后加的*/
public static CityTest Value2CityTest(int value) {
for (CityTest citytest : CityTest.values()) {
if (citytest.value == value) {
return citytest;
}
}
throw new IllegalArgumentException("无效的value值: " + value + "!");
}
}
我们知道,在实现typeHandler时,需要用到两个方法一个方法将javaType转成jdbcType,另一个方法将jdbcType转成javaType。
将javaType转成jdbcType我们可以用CityTest中的getValue(),获取当前CityTest对象的value值即可。 那么现在要增加一个方法,将jdbcType转成javaType(这样就定义了方法的入参和返回类型)。因此我们增加了Value2CityTest方法。
再定义转换器typeHandler的实现类:
public class CityTestTypeHandler extends BaseTypeHandler<CityTest> {
@Override
public CityTest getNullableResult(ResultSet rSet, String columnName)
throws SQLException {
return CityTest.Value2CityTest(rSet.getInt(columnName));
}
@Override
public CityTest getNullableResult(ResultSet rSet, int columnIndex)
throws SQLException {
return CityTest.Value2CityTest(rSet.getInt(columnIndex));
}
@Override
public CityTest getNullableResult(CallableStatement cStatement, int columnIndex)
throws SQLException {
return CityTest.Value2CityTest(cStatement.getInt(columnIndex));
}
@Override
public void setNonNullParameter(PreparedStatement pStatement, int index,
CityTest citytest, JdbcType jdbcType) throws SQLException {
pStatement.setInt(index, citytest.getValue());
}
}
接下来在myBatis配置文件中注册
<!-- 注册自定义类型处理器 -->
<typeHandlers>
<typeHandler handler="twm.mybatisdemo.type.CityTestTypeHandler" />
</typeHandlers>
然后就可以使用了。
补充:
实际使用中并不一定要求显示声明 typeHandler=org.apache.ibatis.type.CityTestTypeHandler,系统会根据类型以及注册的typeHandler自动识别。