刚入手spring-boot还不太熟练,先弄了个空的框架,然后写了个简单的用户查询,没啥挑战性。然后想起来之前一直对枚举不太了解,而用户的性别正好可以用枚举类型来表示(male, female)。于是就开始了自己的挖坑之旅。 本文主要分为三个部分:
假设目前有一张user_info表,我们需要将用户的性别存到数据库里,我们都知道“性别”只可能有两种取值:male或者famale,一般不会出现第三种,这就是我们所知道的枚举类型。可是将male和famale存到数据库里显然不太优雅,也会浪费大量数据库存储空间。我们可以约定0/1分别表示male和female,这样就可以用一个bit就存储原本需要多个字节存储的数据。
MyBatis内置了两个枚举转换器分别是:org.apache.ibatis.type.EnumTypeHandler
和org.apache.ibatis.type.EnumOrdinalTypeHandler
。
EnumTypeHandler
这是默认的枚举转换器,该转换器将枚举实例转换为实例名称的字符串,即将UserSex.MALE
转换MALE
。
EnumOrdinalTypeHandler
顾名思义这个转换器将枚举实例的ordinal属性作为取值,即UserSex.MALE
转换为0,UserSex.FEMALE
转换为1。
使用它的方式是在MyBatis配置文件中定义:
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.example.entity.enums.ComputerState"/>
typeHandlers>
看上去第二种好像已经满足我们的需求。可是,有时候我们需要存储更多的状态,如存储一个订单的状态,常见的有创建、交易中、支付成功、支付失败等等状态。我们定义这样一个枚举类:
public enum OrderStatusEnum {
CREATE(0),
PAYING(1),
IN_PROGRESS(2),
FAILED(3),
REVERSED(4);
private int value;
OrderStatusEnum(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
定义的状态变多之后,数字表述的含义就没那么清晰明白,所以,我们需要对每个状态加上描述文字,可是这样就无法使用mybatis自带枚举类型转换器,需要我们自己设计一个枚举类型转换器,还好设计mybatis的大叔早就预料到了这种情况,所以特意留了相关的接口。
MyBatis提供了org.apache.ibatis.type.BaseTypeHandler
类用于我们自己扩展类型转换器,上面的EnumTypeHandler
和EnumOrdinalTypeHandler
也都实现了这个接口。程序设计如下所示
枚举类
public enum OrderStatusEnum {
CREATE(0, "创建"),
PAYING(1, "支付中"),
IN_PROGRESS(2, "支付成功"),
FAILED(3, "支付失败"),
REVERSED(4, "取消订单");
private int value;
private String desc;
OrderStatusEnum(int value, String desc) {
this.value = value;
this.desc = desc;
}
public int getValue() {
return value;
}
public String getDesc() {
return desc;
}
}
实体类
public class OrderInfo {
private int id;
private OrderStatusEnum orderStatusEnum;
}
准备工作做的差不多了,是时候开始编写转换器了。
BaseTypeHandler 一共需要实现4个方法:
// 用于定义设置参数时,该如何把Java类型的参数转换为对应的数据库类型
void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)
// 用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的Java类型
T getNullableResult(ResultSet rs, String columnName)
// 用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的Java类型
T getNullableResult(ResultSet rs, int columnIndex)
// 用定义调用存储过程后,如何把数据库类型转换为对应的Java类型
T getNullableResult(CallableStatement cs, int columnIndex)
具体实现如下
public class EnumOrderStatusHandler extends BaseTypeHandler<OrderStatusEnum> {
/**
* 设置配置文件设置的转换类以及枚举类内容,供其他方法更便捷高效的实现
* @param type 配置文件中设置的转换类
*/
public EnumOrderStatusHandler(Class<OrderStatusEnum> type) {
if (type == null)
throw new IllegalArgumentException("Type argument cannot be null");
}
/**
* 用于定义设置参数时,该如何把Java类型的参数转换为对应的数据库类型
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, OrderStatusEnum parameter, JdbcType jdbcType) throws SQLException {
// 根据数据库存储类型决定获取类型,本例子中数据库中存放int类型
// ps.setString
ps.setInt(i, parameter.getValue());
}
/**
* 用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的Java类型
*/
@Override
public OrderStatusEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 根据数据库存储类型决定获取类型,本例子中数据库中存放int类型
// String i = rs.getString(columnName);
int i = rs.getInt(columnName);
if (rs.wasNull()) {
return null;
} else {
// 根据数据库中的值,定位Enum子类
return locateEnum(i);
}
}
/**
* 用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的Java类型
* @param rs
* @param columnIndex
* @return
* @throws SQLException
*/
@Override
public OrderStatusEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
// 根据数据库存储类型决定获取类型,本例子中数据库中存放int类型
// String i = rs.getString(columnIndex);
int i = rs.getInt(columnIndex);
if (rs.wasNull()) {
return null;
} else {
// 根据数据库中的值,定位Enum子类
return locateEnum(i);
}
}
/**
* 用定义调用存储过程后,如何把数据库类型转换为对应的Java类型
* @param cs
* @param columnIndex
* @return
* @throws SQLException
*/
@Override
public OrderStatusEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
// 根据数据库存储类型决定获取类型,本例子中数据库中存放int类型
// String i = cs.getString(columnIndex);
int i = cs.getInt(columnIndex);
if (cs.wasNull()) {
return null;
} else {
// 根据数据库中的值,定位Enum子类
return locateEnum(i);
}
}
/**
* 枚举类型转换
* @param value 数据库中存储的自定义属性
* @return value对应的枚举类
*/
private OrderStatusEnum locateEnum(int value) {
for (OrderStatusEnum status : OrderStatusEnum.values()) {
if (status.getValue() == value) {
return status;
}
}
throw new IllegalArgumentException("未知的枚举类型:" + value);
}
}
最后是使用
第一种方式:指定哪个类使用我们自己编写转换器进行转换,在MyBatis配置文件中配置如下:
<typeHandlers>
<typeHandler handler="com.example.typeHandler.EnumOrderStatusHandler " javaType="com.example.entity.enums.OrderStatusEnum"/>
typeHandlers>
第二种方式:修改指定xml文件,指定的Mapper生效
<result column="status" property="orderStatusEnum"
typeHandler="com.example.typeHandler.EnumOrderStatusHandler"/>
<insert id="insert" parameterType="com.example.entity.OrderInfo">
INSERT INTO
order_test
(status)
VALUES (
#{orderStatusEnum, typeHandler=com.example.typeHandler.EnumOrderStatusHandler, jdbcType=INTEGER}
)
insert>
ps:通用转换处理器可以参考第三篇博客
如何在MyBatis中优雅的使用枚举
Java、Mysql、MyBatis 中枚举 enum 的使用
mybatis枚举自动转换(通用转换处理器实现)