一.typeHandler简介
在JDBC中,需要在PreparedStatement对象中设置那些已经编译过的SQL语句的参数.执行sql后,会通过ResultSet对象获取得到数据库的数据,而这些Mybatis是根据数据的类型通过typeHandler来实现的.在typeHandler中,分为jdbcType和javaType,其中jdbcType用于定义数据库类型,而javaType用于定义java类型,那么typeHandler的作用就是承担jdbcType和javaType之间的相互转换.
在mybtis中存在系统定义typeHandler和自定义typeHandler.mybatis会根据JavaType和数据库的jdbcType来决定采用哪个typeHandler处理这些转换规则.系统提供的typeHandler能覆盖大部分场景的要求,但是有些情况下是不够的,比如我们有特殊的转换规则.枚举类型就是这样.
二.系统自定义的typeHandler
Mybatis内部定义了许多有用的typeHandler.,这些转换器都在TypeHandlerRegistry类中注册
我们可以在这个类的构造函数中看到内置的typeHandler
三.自定义typeHandler
1.定义一个枚举类型
首先我们需要一个枚举类型
public enum SexEnum {
MALE(1, "男"),
FEMALE(0, "女");
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
SexEnum(int id, String name) {
this.id = id;
this.name = name;
}
public static SexEnum getSexById(int id) {
for (SexEnum sex : SexEnum.values()) {
if (sex.getId() == id) {
return sex;
}
}
return null;
}
}
2.实现typeHandler接口(第一种实现自定义typeHandler的方法)
在mybatis中要自定义typeHandler,其中一种方法是实现org.apache.ibatis.type.TypeHandler接口
public interface TypeHandler<T> {
void setParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;
T getResult(ResultSet var1, String var2) throws SQLException;
T getResult(ResultSet var1, int var2) throws SQLException;
T getResult(CallableStatement var1, int var2) throws SQLException;
}
这个接口中有4个方法
- 其中T是泛型.专指javaType,比如我们需要string的时候,那么实现类可以写为implements TypeHandler<String>.
- setParameter方法,是使用typeHandler通过PreparedStatement对象进行设置SQL参数的时候使用的具体方法,其中i是参数在SQL的下标,parameter是参数,jdbcType是数据库类型.
- 其中有3个getResult的方法,它的作用是从jdbc结果集中获取数据进行转换,要么使用列名(columnName)要么使用下标(columnIndex)获取数据库的数据,其中最后一个getResult方法是存储过程专用的.
下面的代码用于转换刚刚定义的枚举:
public class SexTypeHandler2 implements TypeHandler<SexEnum> {
public void setParameter(PreparedStatement ps, int i, SexEnum parameter,
JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getId());
}
public SexEnum getResult(ResultSet rs, String columnName)
throws SQLException {
int id = rs.getInt(columnName);
return SexEnum.getSexById(id);
}
public SexEnum getResult(ResultSet rs, int columnIndex) throws SQLException {
int id = rs.getInt(columnIndex);
return SexEnum.getSexById(id);
}
public SexEnum getResult(CallableStatement cs, int columnIndex)
throws SQLException {
int id = cs.getInt(columnIndex);
return SexEnum.getSexById(id);
}
}
3.实现自定义typeHandler的第二种方法
其实我们看Mybatis系统定义的typeHandler我们会发现他们都是继承了一个叫BaseTypeHandler的类
看一下BaseTypeHandler的源码
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
protected Configuration configuration;
public BaseTypeHandler() {
}
public void setConfiguration(Configuration c) {
this.configuration = c;
}
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException var7) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. Cause: " + var7, var7);
}
} else {
try {
this.setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception var6) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . Try setting a different JdbcType for this parameter or a different configuration property. Cause: " + var6, var6);
}
}
}
public T getResult(ResultSet rs, String columnName) throws SQLException {
Object result;
try {
result = this.getNullableResult(rs, columnName);
} catch (Exception var5) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + var5, var5);
}
return rs.wasNull() ? null : result;
}
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
...
}
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
...
}
public abstract void setNonNullParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;
public abstract T getNullableResult(ResultSet var1, String var2) throws SQLException;
public abstract T getNullableResult(ResultSet var1, int var2) throws SQLException;
public abstract T getNullableResult(CallableStatement var1, int var2) throws SQLException;
}
简单分析一下这个类(篇幅原因省略部分代码)
- BaseTypeHandler是个抽象类,需要子类去实现其定义的4个抽象方法.而他本身实现了typeHandler接口的4个方法.
- getResult方法,非空结果集是通过getNullableResult方法获取的.如果判断为空,则返回null.
- setParameter方法,当参数parameter和jdbcType同时为空时,Mybatis将抛出异常,如果能明确jdbcType,则会进行行空设置,如果参数不为空,那么它将采用setNonNullParameter方法设置参数.
- getNullableResult方法用于存储过程
于是我们可以继承这个类实现该类的四个抽象方法来实现自定义typeHandler
public class SexTypeHandler extends BaseTypeHandler<SexEnum> {
@Override
public SexEnum getNullableResult(ResultSet rs, String name) throws SQLException {
int sex = rs.getInt(name);
return SexEnum.getSexById(sex);
}
@Override
public SexEnum getNullableResult(ResultSet rs, int index) throws SQLException {
int sex = rs.getInt(index);
return SexEnum.getSexById(sex);
}
@Override
public SexEnum getNullableResult(CallableStatement cs, int index) throws SQLException {
int sex = cs.getInt(index);
return SexEnum.getSexById(sex);
}
@Override
public void setNonNullParameter(PreparedStatement ps, int index, SexEnum sex, JdbcType jdbcType) throws SQLException {
ps.setInt(index, sex.getId());
}
}
四.配置使用自定义typeHandler
1.第一种方法
在mybatis的主配文件中加入如下配置
handler是自定义typeHandler的路径
<typeHandlers>
<typeHandler handler="com.exia.typeHandler.SexTypeHandler2" javaType="SexEnum" jdbcType="int" />
</typeHandlers>
如果转化器特别多的话,可以考虑扫描包的形式
<typeHandlers>
<package name="com.exia.typeHandler"/>
</typeHandlers>
这样的话,这个包下的所有typeHandler都会被扫描到,但扫包的话就不能指定JavaType和jdbcType了.
我们需要在自定义的TypeHandler上加入注解指定jdbcType和JavaType
@MappedTypes(SexEnum.class)
@MappedJdbcTypes(JdbcType.INTEGER)
2.第二种方法
在映射时,直接指定typeHandler属性