SpringBoot-MyBatis - Java枚举类型 <---> MySQL Int,建立 类型处理器(typeHandlers)
场景:
MySQL里的某一个字段,比如:status状态,一共有5个状态,我们会在MySQL里 建立 status(int)字段,1、2、3、4、5 来标记5种状态;利用MyBatis在自动代码生成器生成实体类后,status会是integer类型;
而status 只有5种状态,且程序很多地方常用,于是我们建立status枚举,返回给终端的json里,也是返回枚举字符串,只是存MySQL里用int来存,节省空间,提升查询效率;好了多余的废话不说了,直接上代码:
1. 建立一个通用的枚举接口:
/**
* MySQL字段对应的枚举类需要实现这个接口
*/
public interface ISqlEnum {
/**
* 获取对应的MySQL里的值
*/
Integer getSqlInt();
}
2.定义一个枚举,实现枚举接口:
/**
* 平台类型,以后尽量使用这个来取代ShopType,枚举类;
* ShopType会等待合适的时机变更为:平台内部店铺类型如:专营店、自营店、旗舰店等
*/
public enum PlatformType implements ISqlEnum {
OFFLINE("线下", 9),
/**
* 淘宝/天猫
*/
TAOBAO("淘宝/天猫", 1),
/**
* 京东
*/
JINGDONG("京东", 2),
/**
* 拼多多
*/
PINDUODUO("拼多多", 3),
/**
* 抖音
*/
DOUYIN("抖音", 4),
/**
* 微信视频号
*/
WEIXIN("微信视频号", 5),
/**
* 阿里1688
*/
ALI1688("阿里1688", 6),
/**
* 快手
*/
KUAISHOU("快手", 7),
/**
* 苏宁
*/
SUNING("苏宁", 8);
/**
* 线下
*/
/**
* 店铺类型的英文名称就是枚举的小写,中文名称存储一下,方便直接返回给客户端;
*/
private String cnName;
/**
* MySQL里的int值
* 平台类型:1(淘宝),2(京东),3(拼多多),4(抖音),5(微信视频号),6(阿里1688),7(快手),8(苏宁),9(线下)
*/
private Integer sqlInt;
/**
* 私有构造方法
*/
private PlatformType(String cnName, Integer sqlInt) {
this.cnName = cnName;
this.sqlInt = sqlInt;
}
/**
* 对外公开获取中文名称的方法
*/
public String getCnName(){
return this.cnName;
}
@Override
public Integer getSqlInt() {
return this.sqlInt;
}
}
3. 定义一个类型处理器:类型处理器(typeHandlers),根据mybatis官方的文档,它默认已经有了2个枚举类型处理器:https://mybatis.net.cn/configuration.html#typeHandlers
A. EnumTypeHandler 这个官方默认的,通过官方的说明,源码得出结论:就是说 你MySQL里 若这个status字段是varchar类型,你向MySQL里存的是枚举的字符串的话,举例:PlatformType.TAOBAO,
你向MySQL里 存的是 TAOBAO 这个字符串的话,那么你可以直接在实体类里使用PlatformType枚举,而不用再做任何操作,因为TAOBAO这个字符串是可以直接与枚举类互转的;
B. EnumOrdinalTypeHandler 这个官方默认的,意思是:若你向MySQL里存的是枚举类的序号值,MySQL里是int 或兼容int的类型,也是不用做任何操作,在实体类里就可以使用 这个枚举;
因为序号也是可以直接与枚举互转的;
官方能做的也就A,B这2个了,其它的官方的也无能为力,A 首先不合适,正如官网所说,我们的DBA更倾向于枚举字段,在MySQL里用 int 类型来存储,节省空间,提升查询效率;
接下来说下B,B虽然MySQL里存的是int了,但是也太简陋了,只能按枚举的序号来,那么就意味着枚举类里的顺序 TAOBAO,JINGDONG,PDD你不能改变,一旦变成DOUYIN,TAOBAO,JINGDONG,PDD
则会出大乱子了!所以B还是简陋了,适合刚设计时就永远不会发生变化的枚举类;
细想你会发现官方能做的也只能这样了,因为枚举类是你自由定义的,要想 MySQL -- 枚举类,枚举类 --> Mysql;官方能做的就是 通过默认的字符串和 序号来转化,其它的转化是你自己自定义的,官方又如何能知道呢;
根据官方的提示,我们参考 官方默认的 两个枚举处理器,自定义一个我们自己的泛型枚举处理器;
public class SqlEnumHandler<E extends SqlEnum> extends BaseTypeHandler<E> {
/**
* 把枚举实例存入map,而不是官方的数组,因为数组查找的时候需要遍历,
* 官方使用数组是因为官方按枚举序号来的,即使使用数组也不需要遍历,
* 而我们不一定,sqlInt我们是自定义的,不是枚举实例的序号;
*/
private final Map<Integer, E> enumMap;
/**
* 官方的框架会自动调用这个方法,来构建枚举实例map
*/
public SqlEnumHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
//拿到类型实例,写入map
enumMap = new HashMap<>();
for (E enumConstant : type.getEnumConstants()) {
enumMap.put(enumConstant.getSqlInt(), enumConstant);
}
}
/**
* 1.若MySQL里的字段设置了不为Null,而你传入的枚举是Null,那么应该如何写入MySQL;(这个由你写代码决定,就是本方法)
* 2.若MySQL里的字段设置了可以为Null,而你传入的枚举也是Null,这种官方自动处理了,自动写入Null,不需要你写额外的代码;
* --------------------------------------------------------------
* 我们强制规定,若MySQL里一个字段是枚举,则这个字段【必须】设置成不为Null;
* 因为MySQL层面的问题,要在MySQL层面解决,举例:status都已经是一个枚举字段了,一共就5种状态,
* 是定死的,你非要搞成可以null,这就是你自己的脑袋有问题;我们这里不考虑Null的问题;
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getSqlInt());
}
/**
* 1.若MySQL里字段设置了可以为Null,当查询出来的结果为Null,你要转成哪个枚举实例;(需要你写代码来决定,就是本方法)
* 2.若MySQL里字段设置了可以为Null,查询出来的结果不为Null,这种的也需要你写代码来决定,就是本方法;
* -----------------------------
* 我们强制MySQL里的枚举字段不可以为Null,所以不考虑Null的问题,以下都是同理;
*/
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
return enumMap.get(rs.getInt(columnName));
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return enumMap.get(rs.getInt(columnIndex));
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return enumMap.get(cs.getInt(columnIndex));
}
}
4. spring boot 的 application.properties 加上配置,配置一下枚举类型处理器,你会发现spring boot 会有这个配置,原因是为什么呢,肯定是mybatis官方也知道他提供的那2个枚举类型处理器,弱爆了,
而这个处理器,只能由用户自己来编写代码,所以他有一个配置来让你指定;
#你指定一下你自己写的那个SqlEnumHandler类就行了;
mybatis.configuration.default-enum-type-handler
================================
以上4步搞定后,就可以了,测试: