在这篇文章中,我们已经知道如何使用枚举类直接接受前端的数字类型参数,省去了麻烦的转换。如果数据库需要保存枚举类的code,一般做法也是代码中手动转换,那么能不能通过某种机制,省去转换,达到代码中直接保存枚举对象,但是数据库中保存的却是code值呢。即我们的整体目标如下
实际上Mybatis对枚举类有一定的支持,在官网中看到对枚举类的支持有两种:EnumTypeHandler和EnumOrdinalTypeHandler。前者是保存枚举的name,后置是保存枚举的ordinal值。这两个都不满足我们的需求,仿照它们,我们洗一个自己的TypeHandler。
自定义枚举TypeHandler
还是使用原先的数据对象
public class Product {
private Status status;
private String name;
// getter and setter
}
// BaseEnumDeserial 见https://blog.csdn.net/weixin_41535316/article/details/142426433
@JsonDeserialize(using = BaseEnumDeserial.class)
public interface BaseEnum {
Integer getCode();
}
pu
blic enum Status implements BaseEnum {
ON_LINE(1000, "在线"),
OFF_LINE(2000, "下线")
;
private int code;
private String desc;
Status(int code, String desc) {
this.code = code;
this.desc = desc;
}
public static Status getByCode(int code) {
final Status[] values = Status.values();
for (int i = 0; i < values.length; i++) {
if (values[i].code == code) {
return values[i];
}
}
throw new RuntimeException("不合法的code值");
}
@Override
public Integer getCode() {
return code;
}
}
配置数据库连接
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/stu?serverTimezone=GMT&useSSL=false
username: root
password: root
mybatis:
# 自定义的typeHandler所在包位置
type-handlers-package: com.example.mybatis.typehandle
mapper-locations: classpath:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
创建表
CREATE TABLE `product` (
`name` varchar(255) NULL,
`status` integer NULL
);
创建Mapper
@Mapper
public interface ProductMapper {
int insert(Product product);
Product getByName(String name);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatis.mapper.ProductMapper">
<resultMap id="product" type="com.example.mybatis.entity.Product">
<result property="status" jdbcType="INTEGER" column="status"/>
<result property="name" jdbcType="VARCHAR" column="name"/>
</resultMap>
<insert id="insert" parameterType="com.example.mybatis.entity.Product">
insert into product(name, status) values(#{name}, #{status})
</insert>
<select id="getByName" parameterType="string" resultType="com.example.mybatis.entity.Product">
select name, status from product where name = #{name}
</select>
</mapper>
定义接口
@Autowired
ProductMapper productMapper;
@PostMapping("/insertProduct")
@ResponseBody
public void insertProduct(@RequestBody Product product) {
System.out.println(product.getStatus());
System.out.println(product.getName());
final int insert = productMapper.insert(product);
System.out.println(insert);
System.out.println("ok");
}
@GetMapping("/getProduct")
@ResponseBody
public void getProduct(@RequestParam String name) {
final Product product = productMapper.getByName(name);
System.out.println(product.getStatus());
System.out.println(product.getName());
System.out.println("ok");
}
定义通用TypeHandler
通用TypeHandler同样面临在运行时怎么确定要转成哪种具体枚举类的问题,不同于jackson的运行时创建反序列化器,Mybatis是在项目启动时创建了所有的TypeHandler,且对于枚举类,会根据具体对象创建出不同的TypeHandler。
通用TypeHandler如下:
@MappedTypes(BaseEnum.class)
@MappedJdbcTypes(value = {JdbcType.SMALLINT,JdbcType.TINYINT,JdbcType.INTEGER}, includeNullJdbcType = true)
public class BaseEnumTypeHandler<T extends BaseEnum> extends BaseTypeHandler<BaseEnum> {
private final Class<T> type;
private final T[] enums;
/**
* 对于枚举,会优先使用Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
* 获取构造函数,如果没有,会获取午无参构造函数,这样就可以为同一个接口下的不同实现类创建不同的TypeHandler了
*/
public BaseEnumTypeHandler(Class<T> clazz) {
this.type = clazz;
enums = type.getEnumConstants();
}
/**
* 这里时设置参数 i是参数位置,parameter是外层传入的真实值,可以处理后再存入数据库
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, BaseEnum parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getCode());
}
/**
* rs.getInt(columnName)拿到了数据库中保存的值,处理后得到返回给上层的类型 T
*/
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
final int code = rs.getInt(columnName);
for (int i = 0; i < enums.length; i++) {
if (enums[i].getCode() == code) {
return enums[i];
}
}
return null;
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
final int code = rs.getInt(columnIndex);
for (int i = 0; i < enums.length; i++) {
if (enums[i].getCode() == code) {
return enums[i];
}
}
return null;
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
final int code = cs.getInt(columnIndex);
for (int i = 0; i < enums.length; i++) {
if (enums[i].getCode() == code) {
return enums[i];
}
}
return null;
}
}
例如我们有两个枚举类Status和GenderEnum都继实现了BaseEnum接口,Mybatis会创建两个BaseEnumTypeHandler。
另外需要注意的是,如果数据库中的枚举是NULL,那么ResultSet 的getInt()方法会返回0,而不是NULL。所以我们的枚举类最好不要使用0作为一个有意义的code值。