一直很好奇在 Java 类中定义的 枚举类型的属性通过 Mybatis 是怎么映射到 MySQL的.
于是做了一下尝试:
新建一个 Java 类
// 定义一个用户的枚举类
public class UserDTO{
private String name;
private GenderEnum gender;
}
// 定义一个性别的枚举类
public enum GenderEnum{
MALE,FEMALE;
}
然后在 数据库中创建一张表
create table user(name varchar(255) primary key,gender varchar(255));
然后编写 mybatis 映射文件,并执行调用方法
<select id="select" resultType="com.example.demo23.model.User">
select * from user;
</select>
调用该条 SQL ,并追踪代码
最后发现在 DefaultResultSetHandler 中会调用 EnumTypeHandler 去执行映射关系
然后调用 EnumTypeHandler 中的 getNullableResult 去实例化对应的属性值
在Mybatis映射中并不是直接的 MySQL 中字段的 枚举类型直接映射到 Java 中的枚举类型的,一方面由于 MySQL 的枚举类型在定义后具体枚举修改起来较为复杂,所以对应Java枚举可以映射为 MySQL 中的整型类型或者字符类型,麻烦的对象映射可以交由 Mybatis
去解决。
对于 Enum
类型 Mybatis
其实是对应的有两种处理器的,分别对应在数据库中存储的两种类型方式。当对于枚举数据的存储类型为varchar
类型的时候,使用 EnumTypeHandler
进行获取枚举类型;当对于枚举数据的存储类型为整型类型如int
,bigint
,smallint
等类型时,使用 EnumOrdinalTypeHandler
进行处理枚举类型。
两种数据类型都可以使用,对于 varchar
类型,在查看数据时会更加直观一些,但会增加数据的使用空间并且增加索引的时间复杂度;对于整型类型,会方便通过数据创建索引,但是在直接从数据库查看数据时获取的结果会比较抽象。
EnumOrdinalTypeHandler
枚举序数方式处理器EnumTypeHandler
枚举名称方式处理器
EnumOrdinalTypeHandler
整型类型(BIGINT
,INT
,SMALLINT
) --> Java枚举
这种类型的枚举处理器会自动将数据库中保存为数值类型的字段映射为Java中对应的枚举类型。
映射逻辑:
当数据库中保存字段的值为 NULL
时,映射的枚举值为 null
反之,则根据具体的代码中枚举定义的顺序进行映射。
// org.apache.ibatis.type.EnumOrdinalTypeHandler#getNullableResult(java.sql.ResultSet, java.lang.String)
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
int ordinal = rs.getInt(columnName);
// 判断表中字段值是否为NULL
// ResultSet 会将部分 Null 值映射为0,所以还需要使用 wasNull 判断一下是否为 Null
if (ordinal == 0 && rs.wasNull()) {
return null;
}
// 根据枚举顺序映射,从0开始
return toOrdinalEnum(ordinal);
}
// int 映射为 Enum ,超出索引的时候,抛出异常
private E toOrdinalEnum(int ordinal) {
try {
return enums[ordinal];
} catch (Exception ex) {
throw new IllegalArgumentException("Cannot convert " + ordinal + " to " + type.getSimpleName() + " by ordinal value.", ex);
}
}
需要注意的问题
在数据库中保存Java 中的枚举值为数值类型需要注意一个问题,就是当我们在表中已经存在保存的枚举值后,修改 Java枚举类有可能导致映射顺序错误。
public enum Country{
china,america;
}
如上所示,如果我已经按照上面的枚举类在数据库中保存了数据的话,那么 0
会被映射为 china
,1
会被映射为 america
。但如果我在这种情况下在枚举china
和 america
中间添加一个japan
的话,那么新的映射关系就会是0
会映射为china
,1
会映射为japan
,2
会映射为america
,从而导致数据库中保存的数据异常。如果存在修改代码的情况为以防万一,最好还是保存Java枚举为数据库中的字符类型。
EnumTypeHandler
字符串(char,varchar,text)–> Java枚举
这种类型的枚举处理器会自动将数据库中字段类型为字符串类型(char,varchar,text)的字段值映射为Java枚举。
映射逻辑:
根据获取到的值,当值为 Null 时返回 null,否则,通过 Enum.valueOf()
进行处理.
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
String s = rs.getString(columnName);
// 当获取到的字段值为 Null 时返回 Null 否则通过 Enum.valueOf 实现映射
// 当无法获取到映射值时,抛出异常
return s == null ? null : Enum.valueOf(type, s);
}
总结
MySQL 中的varchar类型 映射为 Java 中的 Enum 类型时 ,会调用 Enum.valueOf() 方法进行转化,而 Enum.valueOf() 方法会在映射失败时抛出 IllegalArgumentException 异常.