枚举类与MyBatisplus交互 总结

枚举类与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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南窗木心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值