枚举类与MyBatisplus交互
解决什么问题?
当有状态含义业务字段需要存储的数据库时,通常该字段会设置为int类型,用0,1,2,表示各状态,同时后端也会有对应的枚举类,此博客解决的需求便是,直接将Bean字段类型设为枚举类与MyBatisplus交互
@TableName("student")
class Student {
private Integer id;
private String name;
private GradeEnum grade;//数据库grade字段类型为int
}
基础配置
首先想要使用枚举类与MyBatisplus交互,需要将该枚举类包扫描注册一下
mybatis-plus:
# 配置枚举类扫描并注册对应枚举类匹配器
typeEnumsPackage: com.example.demo.enums
这个要指定成自己项目中的路径,不要直接ctrl CV
下策
MyBatisplus自带两种枚举类匹配器
-
EnumTypeHandler,默认的枚举类匹配器,根据枚举类的name进行存储和取出;
-
EnumOrdinalTypeHandler,根据枚举类ordinal(序号)属性进行匹配
以下面这个枚举类为例
public enum ExpressAccessStatus{
notRemove,
remove;
}
-
若使用默认的EnumTypeHandler匹配器,则存到库中的是枚举类字段的name,也就是notRemove和remove两种字符串,取数据也是只有取到这两种字符串才能反序列化成枚举类对象。
不符合我们的需求
-
若使用EnumOrdinalTypeHandler匹配器,首先我们要知道,枚举类的字段是有编号的,比如notRemove编号为0,remove编号为1,按照从上往下的顺序编号
而这个匹配器是根据编号匹配的,也就是存到库中是该枚举对象对应的编号
也算是满足了我们的需求了。只不过需要我们写枚举类时注意排一下序。
在使用上述的两种自带的枚举类匹配器时,需要在配置文件中指定(默认的匹配器不需要指定)
mybatis-plus:
configuration:
default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
中策
上述EnumOrdinalTypeHandler可以解决我们的需求,不过不太灵活,还需要写枚举类时候注意排序
Mybatisplus想到了这些,于是提供了一个注解@EnumValue
使用该注解需要注意版本
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
</dependency>
@EnumValue作用是可以将其标注在想要与Mybatisplus交互的枚举类属性上
public enum ExpressAccessStatus{
//未取出
notRemove(99),
//已取出
remove(100);
@EnumValue
private int value;
ExpressAccessStatus(int value) {
this.value = value;
}
}
这样,枚举类的设置就无需注重顺序了,可以随意给value值设置,交互时便会根据value值寻找对应的枚举类对象了
@EnumValue其实是由于MyBatisplus内部设置了对应的自定义枚举属性的枚举类匹配器,故使用时,配置文件中就不需要去指定匹配器了
#使用@EunmValue指定匹配字段时无需再在配置指定
#default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
顺便,如果该枚举类还想要控制与前端的交互,可以使用@JsonValue指定序列化的字段,否则默认会返回name字符串
上策
上策的灵活性通用性,则要远胜前两种了,但相对也要更加耗费些精力
我们知道Mybatis的底层其实是对JavaType与数据类型做了一些转化,其基础的抽象类便是BaseTypeHandler类,而上文中提到的两种MyBatisplus内置的枚举类匹配器其实就是实现了BaseTypeHandler类,故我们也可以写一个相似的匹配器来自定义我们的枚举匹配策略
我们先看一下EnumOrdinalTypeHandler枚举匹配器的源码
/**
* Copyright 2009-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author Clinton Begin
*/
public class EnumOrdinalTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
private final Class<E> type;
private final E[] enums;
public EnumOrdinalTypeHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
this.enums = type.getEnumConstants();
if (this.enums == null) {
throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
}
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.ordinal());
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
int ordinal = rs.getInt(columnName);
if (ordinal == 0 && rs.wasNull()) {
return null;
}
return toOrdinalEnum(ordinal);
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int ordinal = rs.getInt(columnIndex);
if (ordinal == 0 && rs.wasNull()) {
return null;
}
return toOrdinalEnum(ordinal);
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
int ordinal = cs.getInt(columnIndex);
if (ordinal == 0 && cs.wasNull()) {
return null;
}
return toOrdinalEnum(ordinal);
}
private E toOrdinalEnum(int ordinal) {
try {
return enums[ordinal];
} catch (Exception ex) {
throw new IllegalArgumentException("Cannot convert " + ordinal + " to " + type.getSimpleName() + " by ordinal value.", ex);
}
}
}
简单分析一下其实主要可以分为三部分,
第一部分是toOrdinalEnum()这个私有方法,传入ordinal序号得到对应的枚举常量
第二部分是setNonNullParameter(),是Java类型 转 数据库类型的逻辑,可以看到源码中,是传入ordinal序号,然后set成数据库的int类型
第三部分是getNullableResult()的三个重载方法,是数据库类型 转 Java类型 的逻辑,可以看到源码中是,将int类型数据库结果,通过上述的私有方法,转成枚举常量返回。
内部逻辑基本一样,重载只是区分了数据来源
顺便对于不知道怎么看源码的读者,推荐阅读:https://blog.csdn.net/qq_26558047/article/details/115264624?spm=1001.2014.3001.5506
看懂源码之后,就可以自己写一个自定义的枚举匹配器了,话不多说,上代码看注释
package com.example.demo.utils;
import com.baomidou.mybatisplus.annotation.IEnum;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
//泛型设置为IEnum,将所有需要自定义枚举类匹配器匹配的枚举类实现IEnum接口
public class AutoEnumTypeHandler extends BaseTypeHandler<IEnum> {
private final Class<IEnum> type;
private final IEnum[] enums;
//构造器
public AutoEnumTypeHandler(Class<IEnum> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
//设置枚举常量数组
this.enums = type.getEnumConstants();
if (this.enums == null) {
throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
}
}
//设置Java类型 ——> 数据库类型
@Override
public void setNonNullParameter(PreparedStatement ps, int i, IEnum parameter, JdbcType jdbcType) throws SQLException {
//数据库存储类型为int,故setInt
//第2个参数选择要转化的枚举类的属性,比如要根据value属性匹配,则传入value属性
ps.setInt(i, (Integer) parameter.getValue());
}
//设置数据库类型 ——> 枚举类
//数据库值来源为 JDBC结果集中根据列名查询
@Override
public IEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
int value = rs.getInt(columnName);
return rs.wasNull() ? null : toValueOfEnum(value);
}
//设置数据库类型 ——> 枚举类
//数据库值来源为 JDBC结果集中根据列下标查询
@Override
public IEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int value = rs.getInt(columnIndex);
return rs.wasNull() ? null : toValueOfEnum(value);
}
//设置数据库类型 ——> 枚举类
//数据库值来源为 存储过程结果集,根据列下标查询
@Override
public IEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
int value = cs.getInt(columnIndex);
return cs.wasNull() ? null : toValueOfEnum(value);
}
/**
* 私有方法 根据匹配字段value找到对应的枚举对象
* @param value
* @return
*/
private IEnum toValueOfEnum(int value) {
try {
//遍历该枚举类的所有常量
for (IEnum e : enums) {
if((Integer) e.getValue() == value){
//value值相等,则匹配到了对应的枚举常量
return e;
}
}
throw new IllegalArgumentException("未知的枚举类型:" + value);
} catch (Exception ex) {
throw new IllegalArgumentException("Cannot convert " + value + " to " + type.getSimpleName() + " by value.", ex);
}
}
}
枚举类:
import com.baomidou.mybatisplus.annotation.IEnum;
public enum ExpressAccessStatus implements IEnum<Integer> {
//未取出
notRemove(1),
//已取出
remove(0);
private int value;
ExpressAccessStatus(int value) {
this.value = value;
}
/**
* 枚举数据库存储值
*/
@Override
public Integer getValue() {
return this.value;
}
}
写好我们的自定义的枚举匹配器后,在配置中指定路径一下即可使用
mybatis-plus:
configuration:
default-enum-type-handler: com.example.demo.utils.AutoEnumTypeHandler