mybatis 默认不支持枚举与数据库字段自动转换,需自定义 typehandler;推荐继承 basetypehandler 实现 setnonnullparameter 和三个 getnullableresult 方法,并通过泛型基类+反射适配 code/name 两种序列化方式,结合全局或局部注册使用。

MyBatis 默认不支持 Java 枚举与数据库字段(如 VARCHAR 或 INTEGER)的自动双向转换,需要通过自定义 TypeHandler 实现。核心是继承 BaseTypeHandler<e></e>,重写四个关键方法:设置参数(setNonNullParameter)和获取结果(getNullableResult 的三个重载)。下面分步骤说明如何安全、可复用地实现。
定义枚举并约定序列化规则
推荐使用带业务含义的字段(如 code 或 name)而非序号,避免因枚举顺序变动导致数据错乱。例如:
public enum Gender {
UNKNOWN(0, "未知"),
MALE(1, "男"),
FEMALE(2, "女");
private final int code;
private final String desc;
Gender(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() { return code; }
public String getDesc() { return desc; }
public static Gender fromCode(int code) {
for (Gender g : values()) {
if (g.code == code) return g;
}
return UNKNOWN;
}
}
编写泛型安全的 TypeHandler
为避免为每个枚举都写一个 handler,可抽象出通用基类。关键点:构造时传入枚举类,并利用反射查找 fromCode 或 valueOf 方法:
- 若数据库存的是字符串(如
"MALE"),优先调用Enum.valueOf(clazz, value) - 若存的是数字(如
1),需确保枚举有静态工厂方法(如fromCode),并在 handler 中显式调用 - 务必处理
null值,避免 NPE
public class EnumCodeTypeHandler<e extends enum>> extends BaseTypeHandler<e> {
private final Class<e> type;
private final Function<object e> valueOfFunc;
public EnumCodeTypeHandler(Class<e> type) {
this.type = type;
this.valueOfFunc = buildValueOfFunction(type);
}
private Function<object e> buildValueOfFunction(Class<e> type) {
try {
Method fromCode = type.getMethod("fromCode", int.class);
return obj -> {
if (obj == null) return null;
return (E) fromCode.invoke(null, ((Number) obj).intValue());
};
} catch (Exception e) {
// fallback to name-based
return obj -> obj == null ? null : Enum.valueOf(type, (String) obj);
}
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
if (parameter instanceof EnumWithCode ec) {
ps.setInt(i, ec.getCode());
} else {
ps.setString(i, parameter.name());
}
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
Object value = rs.getObject(columnName);
return value == null ? null : valueOfFunc.apply(value);
}
// 实现另外两个 getNullableResult 重载(按索引、CallableStatement)
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Object value = rs.getObject(columnIndex);
return value == null ? null : valueOfFunc.apply(value);
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Object value = cs.getObject(columnIndex);
return value == null ? null : valueOfFunc.apply(value);
}
}</e></object></e></object></e></e></e>
注册 TypeHandler(XML 或注解方式)
两种常用注册方式,任选其一即可:
- 全局注册(推荐):在 MyBatis 配置文件中添加
<typehandlers><typehandler handler="com.example.EnumCodeTypeHandler" javatype="com.example.Gender"></typehandler></typehandlers>
-
局部指定(Mapper XML 中):在
<resultmap></resultmap>或<parametermap></parametermap>中显式声明
<result column="gender_code" property="gender" javatype="com.example.Gender" jdbctype="INTEGER" typehandler="com.example.EnumCodeTypeHandler"></result>
-
注解方式(@Select/@Insert 等):在方法参数或返回类型上加
@Options不适用;需配合@Results指定
注意事项与最佳实践
避免踩坑的关键细节:
- 不要在 handler 中抛出未检查异常(如
IllegalArgumentException),应返回默认值或null,否则查询中断 - 若枚举字段可能为
null,确保数据库列允许 NULL,并在 handler 的getNullableResult中判空 - 批量插入/更新时,handler 会被反复调用,确保线程安全(无共享可变状态)
- 测试时覆盖边界情况:空值、非法码值、大小写不匹配(字符串模式下)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











