TypeHandler和TypeHandlerRegistry
TypeHandler
大伙都知道Mybatis是对JDBC的封装,那Mybatis是如何处理JDBC类型和Java类型之间的转换的呢?
当在预处理语句(PreparedStatement)中设置一个参数时;
当在结果集(ResultSet)中获取一个值时;
当获取(CallableStatement)处理函数时的返回值时;
都会用类型处理器去处理Java类型和JDBC类型之间的转换。Mybatis默认为我们实现了很多TypeHandler,当我们没有配置指定TypeHandler时,Mybatis会根据参数或者返回结果的不同,默认为我们选择合适的TypeHandler处理。
<typeHandlers>
<!--
当配置package的时候,mybatis会去配置的package扫描TypeHandler
<package name="com.dy.demo"/>
-->
<!-- handler属性直接配置我们要指定的TypeHandler -->
<typeHandler handler=""/>
<!-- javaType 配置java类型,例如String, 如果配上javaType, 那么指定的typeHandler就只作用于指定的类型 -->
<typeHandler javaType="" handler=""/>
<!-- jdbcType 配置数据库基本数据类型,例如varchar, 如果配上jdbcType, 那么指定的typeHandler就只作用于指定的类型 -->
<typeHandler jdbcType="" handler=""/>
<!-- 也可两者都配置 -->
<typeHandler javaType="" jdbcType="" handler=""/>
</typeHandlers>
TypeHandler 源码分析
// 泛型T指代的是要转换的Java类型
public interface TypeHandler<T> {
// 对应着ps.set??(?索引,插入值)
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
// 对应着rs.get??(columnName)
T getResult(ResultSet rs, String columnName) throws SQLException;
// 对应着rs.get??(columnIndex)
T getResult(ResultSet rs, int columnIndex) throws SQLException;
// 对应着cs.get??(columnIndex)
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
现在小编拿一个Mybatis中对其实现的IntegerTypeHander进行举例。
首先看看其继承关系
再看看其实现(实际就是JDBC代码)
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter);
}
@Override
public Integer getNullableResult(ResultSet rs, String columnName) throws SQLException {
int result = rs.getInt(columnName);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Integer getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int result = rs.getInt(columnIndex);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Integer getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
int result = cs.getInt(columnIndex);
return result == 0 && cs.wasNull() ? null : result;
}
}
TypeHandler注册,TypeHandlerRegistry源码分析
Mybatis会根据你使用preparedStatement进行预处理,然后插入数据的类型和返回的结果类型会去自动匹配TypeHandler。那它是怎么注册TypeHandler的呢?使用TypeHandlerRegistry这个类。下面是其源码分析。
主要的三个属性
// 通过JDBC类型映射类型处理器
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
// 通过Java类型和JDBC类型映射唯一的类型处理器
// 从这个map可以看出一个Java类型可以对应多个类型处理器,需要啥可以具体写sql时候进行指定
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
// 这里有所有进行配置的类型处理器,与其class对象进行映射其引用
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
注册方法
核心注册方法
Mybatis自动注册内容
TypeHandler拓展案例
Mybatis提供了我们很多类型处理器,当然我们也可以根据自己的需要写属于自己的类型处理器然后配置使用。下面拿枚举性别类型进行举例。(数据库表中是拿数字0,1,2 去指示性别的,现在希望数字和其表示的含义我们都需要)
先看看表对应的pojo类
@Data
@NoArgsConstructor
public class Person {
private Integer id;
private String name;
private Integer age;
private SexEnum sex;
}
下面是需要转换的Java类型(性别枚举类型)
public enum SexEnum {
UNKNOW(0,"未知"),
MAN(1,"男"),
WOMAN(2,"女");
private Integer value;
private String name;
SexEnum(Integer value, String name) {
this.value = value;
this.name = name;
}
public Integer getValue() {
return value;
}
public String getName() {
return name;
}
public static SexEnum getByValue(Integer value){
for (SexEnum item : values()){
if (item.getValue().equals(value)) {
return item;
}
}
return null;
}
}
写类型处理器,可以选择继承BaseTypeHandler类,也可以选择去实现TypeHandler接口。如果不想在配置文件中去配置对应的Java类型的话,那就使用@MappedTypes
注解进行配置(当然可以配置多个,因为一个处理器可以对应多个Java数据类型嘛,有类似的枚举也可以配置,从而达到复用)。
@MappedTypes(SexEnum.class)
public class SexEnumHandler extends BaseTypeHandler<SexEnum> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, SexEnum parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i,parameter.getValue());
System.out.println("parameter.value:"+parameter.getValue());
}
@Override
public SexEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
Object object = rs.getObject(columnName);
Integer sex = (object!=null && object instanceof Integer)?(Integer) object:null;
return SexEnum.getByValue(sex);
}
@Override
public SexEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Object object = rs.getObject(columnIndex);
Integer sex = (object!=null && object instanceof Integer)?(Integer) object:null;
return SexEnum.getByValue(sex);
}
@Override
public SexEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return null;
}
}
然后在Mybatis核心配置文件中进行配置。
<typeHandlers>
<package name="com.ncpowernode.mybatis.handlertype"/>
</typeHandlers>
测试结果
总结
- 类型处理器内部其实就是JDBC代码,预处理插入数据和获取结果集;
- 可以在核心配置中通过typeHandlers进行对类型处理器的配置;
- 可以通过继承BaseTypeHandler或去实现TypeHanlder接口去实现对类型处理器的拓展;
参考文献
类型处理器,这个你得会玩
typeHandler解析