视频地址:https://www.bilibili.com/video/BV1kf4y1i761?p=15
在开发中,有很多字段使用枚举类型可以更好地表达我们想要的效果。但在实际使用过程中,却存在两个问题
- 枚举参数映射到数据库的时候类型匹配不上
- 接收枚举参数的时候也会存在结果不对的情况
一、前提
1.1、StatusEnum
比如我们有一个这样的枚举
import lombok.Getter;import lombok.ToString;/**
* 状态枚举
*/@Getter@ToStringpublicenum StatusEnum{ENABLE(1,"启用"),DISABLE(0,"禁用");private Integer code;private String name;StatusEnum(Integer code, String name){this.code= code;this.name= name;}}
1-2、实体
定义一个这样的实体
publicclassMenu{/**
* id
*/@TableIdprivate 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;publicinterfaceIBaseEnum<K, V, TextendsEnum<?>>{
KgetCode();
VgetMsg();}
2-2、StatusEnum
StatusEnum改造,实现 通用接口
import lombok.Getter;import lombok.ToString;/**
* 状态枚举
*/@Getter@ToStringpublicenum StatusEnumimplementsIBaseEnum<Integer, String, StatusEnum>{ENABLE(1,"启用"),DISABLE(0,"禁用");private Integer code;private String name;StatusEnum(Integer code, String name){this.code= code;this.name= name;}@Overridepublic StringgetMsg(){returnthis.name;}@Overridepublic IntegergetCode(){returnthis.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;publicclassEnumTypeHandler<EextendsEnum<E>& IBaseEnum>extendsBaseTypeHandler<IBaseEnum>{private Class<E> type;publicEnumTypeHandler(Class<E> type){if(type== null){thrownewIllegalArgumentException("Type argument cannot be null");}else{this.type= type;}}publicvoidsetNonNullParameter(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 EgetNullableResult(ResultSet rs, String columnName)throws SQLException{
String s= rs.getString(columnName);return s== null?null:Enum.valueOf(this.type, s);}public EgetNullableResult(ResultSet rs,int columnIndex)throws SQLException{
String s= rs.getString(columnIndex);return s== null?null:Enum.valueOf(this.type, s);}public EgetNullableResult(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;publicinterfaceIBaseEnum<K, V, TextendsEnum<?>>{
Map<Class<?>, Map<?,?>> map=newLinkedHashMap();defaultvoidinitMap(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=newLinkedHashMap();
tmp.put(code, t);
map.put(t.getClass(), tmp);}}@JsonCreatorstatic<TextendsEnum<?>, K> Tget(Class<T> clazz, K code){if(map.get(clazz)== null){return null;}else{
Object _code= code;if(codeinstanceofString){
_code= code.toString().trim();}return(T) map.get(clazz).get(_code);}}
KgetCode();
VgetMsg();}
3-2、StatusEnum
在枚举初始化的时候注入到IBaseEnum里面去initMap
, 新增序列化方法
import com.fasterxml.jackson.annotation.JsonCreator;import lombok.Getter;import lombok.ToString;/**
* 状态枚举
*/@Getter@ToStringpublicenum StatusEnumimplementsIBaseEnum<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);}@Overridepublic StringgetMsg(){returnthis.name;}@Overridepublic IntegergetCode(){returnthis.code;}@JsonCreatorpublicstatic StatusEnumforValue(Integer code){return IBaseEnum.get(StatusEnum.class, code);}}