Mybatis与数据库交互时,需要对javaType和jdbcType进行相互转换,为预处理语句设置参数时将javaType转换为jdbcType,从结果集中获取值时将jdbcType转换为javaType。Mybatis已经为我们注册了大部分基本类型的typeHandler,通常情况下,不需要我们自定义typeHandler。但有时为了方便,我们会选择自定义typeHandler。
自定义类型处理器typeHandler通常分为三步,详细过程如下:
(1)创建一个自定义typeHandler的类;
(2)在Mybatis配置文件中配置类型处理器,通过
(3)在引射器的XML配置中标识需要用自定义typeHandler处理的参数或者结果。
了解了自定义类型处理器大致步骤之后,我们接下来结合例子详细介绍每一步需要完成的任务,这样可以帮助我们更深刻理解类型处理器。我们可以通过两种方式来创建自定义typeHandler类,一种是继承:org.apache.ibatis.type.BaseTypeHandler,这是因为BaseTypeHandler已经实现了TypeHandler接口;另一种实现接口:org.apache.ibatis.type.TypeHandler。
(1)创建自定义typeHandler类
首先,我们通过第二种方式,也即继承BaseTypeHandler类,创建一个自定义typeHandler类(GenderTypeHandler),同样对通过实现TypeHandler接口的方式进行介绍。为了方便介绍,我们假设需要处理的是性别,数据库(Oracle)中的类型为NUMBER,值为1和2,分别表示男和女(性别的分类远不止两种,为了方便,多多理解),Java类型为String。GenderTypeHandler类代码如下:
@MappedTypes(Integer.class)
@MappedJdbcTypes(JdbcType.NUMERIC)
public class GenderTypeHandler extends BaseTypeHandler{
@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 this.getGender(rs.getString(columnName));
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return this.getGender(rs.getString(columnIndex));
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return this.getGender(cs.getString(columnIndex));
}
private String getGender(String genderCode) {
if("1".equals(genderCode)) {
return "男";
}else if("2".equals(genderCode)) {
return "女";
}else {
return null;
}
}
}
GenderTypeHandler类功能主要体现在两方面,一方面使用预编译(PreparedStatement)设置参数,另一个方面从结果集获取对应的性别,以实现TypeHandler接口的方式创建自定类型处理器的功能也是如此。
需要注意,这里我们使用了注解@MappedTypes和@MappedJdbcTypes,其中,@MappedTypes定义了javaType,指定拦截java类型,此外,我们也可以通过在Mybatis配置中typeHandler的javaType属性定义;@MappedJdbcTypes定义了JdbcTypes,JdbcTypes需要从枚举类org.apache.ibatis.type.JdbcType中获取,同样,我们也可用配置文件中typeHandler的jdbcType属性定义。如果在配置文件中使用typeHandler的属性。注解@MappedTypes与@MappedJdbcTypes定义的会被忽略,也即属性定义的优先级高于注解定义的优先级。
至此,我们可以进行测试,也即跳过第二步,直接进入第三步,在引射器的XML进行配置,代码片段如下:
这里我们运行测试方法输出一下测试结果:
如上,在result中使用typeHandler属性指定类型处理器,创建类型处理器时可以不用添加MappedTypes和MappedJdbcTypes注解。小插曲过后,接下来我们介绍自定义类型处理器的第二步。
(2)在Mybatis配置文件中配置类型处理器
上文我们已经详细介绍了利用注解定义与利用typeHandler属性定义javaType和jdbcType的区别,这里不再赘述。配置typeHandlers时,需要按照Mybatis配置文件指定的标签顺序配置,不然DTD会报错。直接上代码了:
(3)在引射器的XML配置中标识需要用自定义typeHandler处理的参数或者结果
可粗略的分为两种,一种直接在result标签配置typeHandler属性;另一种是配置result标签javaType和jdbcType属性,这种方式需要利用注解或者利用typeHandler属性定义了javaType和jdbcType。利用result标签指定javaType和jdbcType时,result标签的属性javaType和jdbcType的属性值需要与之前定义好的一致,这样Mybatis就会利用自定义的类型处理器为预编译设置参数与获取值了。
通过继承BaseTypeHandler类自定义类型处理器及其过程步骤就介绍完成了,接下来我们介绍通过实现TypeHandler接口的方式自定义类型处理器。
这里我们就不按照上文提到的步骤一步一步详细介绍了,直接从代码开始。首先我们先创建一个枚举类型Gender,并在pojo类型中添加成员变量Gender,Gender代码(省略get、set方法)如下:
public enum Gender {
MALE(1,"男"),
FEMALE(2,"女");
private Integer code;
private String gender;
private Gender(Integer code,String gender) {
this.code = code;
this.gender = gender;
}
public static Gender getDender(Integer code) {
if(1 == code) {
return MALE;
}else if(2 == code) {
return FEMALE;
}else {
return null;
}
}
}
接下来我们创建SexTypeHandler实现TypeHandler接口,并完成实现方法,代码如下:
@MappedTypes(Gender.class)
@MappedJdbcTypes(JdbcType.NUMERIC)
public class SexTypeHandler implements TypeHandler{
@Override
public void setParameter(PreparedStatement ps, int i, Gender parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getCode());
}
@Override
public Gender getResult(ResultSet rs, String columnName) throws SQLException {
return Gender.getDender(rs.getInt(columnName));
}
@Override
public Gender getResult(ResultSet rs, int columnIndex) throws SQLException {
return Gender.getDender(rs.getInt(columnIndex));
}
@Override
public Gender getResult(CallableStatement cs, int columnIndex) throws SQLException {
return Gender.getDender(cs.getInt(columnIndex));
}
}
最后我们在mybatis中添加配置好SexTypeHandler,并在引射器XML配置文件中指定类型javaType和jdbcType,mybatis配置如下:
映射器配置如下:
到这儿就可以进行测试了,咋们先来看看测试结果:
通过实现接口的方式创建类型处理器就完成了。
此外,自定义类型处理器还可以创建处理多个类通用的typeHandler,Mybatis内置的有EnumTypeHandler和EnumOrdinalTypeHandler,前者使用枚举字符串的名称作为参数传递,而后者使用下标作为参数传递。创建处理多个类通用的typeHandler方法需要继承BaseTypeHandler类,并添加一个以类为参数构造器以便Mybatis构造typeHandler时传递实际的类。代码(这里泛型继承Student,这在实际中是不合适的)如下:
public class GenericTypeHandler extends BaseTypeHandler {
private final Class type;
public GenericTypeHandler(Class type) {
if (type == null) {
throw new IllegalArgumentException("type argument cannot be null");
}
this.type = type;
}
// 省略子类需要实现的方法,方法实现与之前的一样
...
}
实际开发中,我们可能会用到自定义类型处理器,通过实现接口和继承类两种方式来完成自定义类型处理器,两种方式无本质上的差别,使用哪一种均可。