【初】MyBatis-Plus 使用枚举参数异常, SpringBoot枚举参数异常
大约 4 分钟
视频地址: https://www.bilibili.com/video/BV1kf4y1i761?p=15
在开发中,有很多字段使用枚举类型可以更好地表达我们想要的效果。但在实际使用过程中,却存在两个问题
- 枚举参数映射到数据库的时候类型匹配不上
- 接收枚举参数的时候也会存在结果不对的情况
一、前提
1.1、StatusEnum
比如我们有一个这样的枚举
import lombok.Getter;
import lombok.ToString;
/**
* 状态枚举
*/
@Getter
@ToString
public enum StatusEnum {
ENABLE(1, "启用"),
DISABLE(0,"禁用")
;
private Integer code;
private String name;
StatusEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
}
1-2、实体
定义一个这样的实体
public class Menu {
/**
* id
*/
@TableId
private Integer id;
/**
* 菜单名称
*/
@NotBlank(message = "菜单名称不能为空")
private String name;
/**
* 启用状态
*/
private StatusEnum status;
}
1-3、使用
其它的参数封装,mapper注入不重要
menuMapper.insert(menu);
1.4、异常
运行上面的代码会报错
### The error occurred while setting parameters
### SQL: INSERT INTO xdx_menu ( name, status, create_at, create_by ) VALUES ( ?, ?, ?, ? )
### Cause: java.sql.SQLException: #HY000
; uncategorized SQLException; SQL state [HY000]; error code [1366]; #HY000; nested exception is java.sql.SQLException: #HY000
二、解决 MyBatis/MyBatis-Plus 使用枚举参数异常
之所以会发生这样的问题,是因为在MyBatis参数拼接的时候,它不知道你这个枚举参数是要传递什么样的值,并且每个人定义枚举的样式可能都不一样。解决办法:
- 写一个
IBaseEnum
接口让所有的枚举都去继承这个接口,这样所有的枚举都有共性了 - 自定义一个枚举处理器,继承
BaseTypeHandler
或 实现TypeHandler
接口 - 配置handler
default-enum-type-handler
在 TypeHandler
里面有一个方法 setParameter
在进行SQL拼接的时候会调用这个方法获取参数。
这里我的做法是继承 BaseTypeHandler 类,它里面其实也是实现了 TypeHandler 接口,对里面一些方法进行了实现
而在 BaseTypeHandler
方法里面重写了 setParameter
方法,里面去调用了一个抽象方法 setNonNullParameter
,我们的实现方法主要是去重写这个方法就好了。
2-1、IBaseEnum
它里面的内容很简单,我们就是要通过这个接口,知道:
- 枚举的key是什么类型
- 枚举的value是什么类型
- 以及枚举是什么
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.LinkedHashMap;
import java.util.Map;
public interface IBaseEnum<K, V, T extends Enum<?>> {
K getCode();
V getMsg();
}
2-2、StatusEnum
StatusEnum改造,实现 通用接口
import lombok.Getter;
import lombok.ToString;
/**
* 状态枚举
*/
@Getter
@ToString
public enum StatusEnum implements IBaseEnum<Integer, String, StatusEnum>{
ENABLE(1, "启用"),
DISABLE(0,"禁用")
;
private Integer code;
private String name;
StatusEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
@Override
public String getMsg() {
return this.name;
}
@Override
public Integer getCode() {
return this.code;
}
}
2-3、EnumTypeHandler
这里面还重写了其它的方法,大家只要看 setNonNullParameter
方法即可,有兴趣的可以自行研究其它的
import com.xdx97.blog.common.enums.IBaseEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class EnumTypeHandler<E extends Enum<E> & IBaseEnum> extends BaseTypeHandler<IBaseEnum> {
private Class<E> type;
public EnumTypeHandler(Class<E> type) {
if(type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
} else {
this.type = type;
}
}
public void setNonNullParameter(PreparedStatement ps, int i, IBaseEnum parameter, JdbcType jdbcType) throws SQLException {
if(jdbcType == null) {
ps.setString(i, parameter.getCode().toString());
} else {
ps.setObject(i, parameter.getMsg(), jdbcType.TYPE_CODE);
}
}
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
String s = rs.getString(columnName);
return s == null?null:Enum.valueOf(this.type, s);
}
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String s = rs.getString(columnIndex);
return s == null?null:Enum.valueOf(this.type, s);
}
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String s = cs.getString(columnIndex);
return s == null?null:Enum.valueOf(this.type, s);
}
}
2-4、application.yml
mybatis-plus:
configuration:
# 枚举处理器
default-enum-type-handler: com.xdx97.blog.common.handler.EnumTypeHandler
三、 SpringBoot枚举参数异常
之所以在接受枚举参数的时候和我们预期的结果不一致是因为系列化和反序列化导致的,SpringBoot默认是使用 jackson 序列化的。我们只需要自定义序列化的过程即可。
3-1、IBaseEnum
在上述的IBaseEnum 中新增2个方法
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.LinkedHashMap;
import java.util.Map;
public interface IBaseEnum<K, V, T extends Enum<?>> {
Map<Class<?>, Map<?, ?>> map = new LinkedHashMap();
default void initMap(K code, T t) {
if (map.containsKey(t.getClass())) {
Map<K, T> tmp = (Map)map.get(t.getClass());
tmp.put(code, t);
map.put(t.getClass(), tmp);
} else {
Map<K, T> tmp = new LinkedHashMap();
tmp.put(code, t);
map.put(t.getClass(), tmp);
}
}
@JsonCreator
static <T extends Enum<?>, K> T get(Class<T> clazz, K code) {
if (map.get(clazz) == null) {
return null;
} else {
Object _code = code;
if (code instanceof String) {
_code = code.toString().trim();
}
return (T) map.get(clazz).get(_code);
}
}
K getCode();
V getMsg();
}
3-2、StatusEnum
在枚举初始化的时候注入到IBaseEnum里面去 initMap
, 新增序列化方法
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.Getter;
import lombok.ToString;
/**
* 状态枚举
*/
@Getter
@ToString
public enum StatusEnum implements IBaseEnum<Integer, String, StatusEnum>{
ENABLE(1, "启用"),
DISABLE(0,"禁用")
;
private Integer code;
private String name;
StatusEnum(Integer code, String name) {
this.code = code;
this.name = name;
initMap(code, this);
}
@Override
public String getMsg() {
return this.name;
}
@Override
public Integer getCode() {
return this.code;
}
@JsonCreator
public static StatusEnum forValue(Integer code) {
return IBaseEnum.get(StatusEnum.class, code);
}
}
关注微信公众号(小道仙97)回复:xdxFrameSimple 获取源码。