在这篇文章中,我们已经知道如何使用枚举类直接接受前端的数字类型参数,省去了麻烦的转换。如果数据库需要保存枚举类的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值");}@Overridepublic Integer getCode() {return code;}
}
配置数据库连接
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/stu?serverTimezone=GMT&useSSL=falseusername: rootpassword: root
mybatis:# 自定义的typeHandler所在包位置type-handlers-package: com.example.mybatis.typehandlemapper-locations: classpath:mapper/*.xmlconfiguration: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>
定义接口
@AutowiredProductMapper productMapper;@PostMapping("/insertProduct")@ResponseBodypublic 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")@ResponseBodypublic 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是外层传入的真实值,可以处理后再存入数据库*/@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, BaseEnum parameter, JdbcType jdbcType) throws SQLException {ps.setInt(i, parameter.getCode());}/*** rs.getInt(columnName)拿到了数据库中保存的值,处理后得到返回给上层的类型 T*/@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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值。