资讯专栏INFORMATION COLUMN

使用MyBatis简化枚举类值的存储和读取

Bryan / 1862人阅读

摘要:内置的枚举处理器为了处理上述遇到的问题,内置了两种,分别是和。将使用枚举实例的值序数值,从开始来和枚举类之间做转换。比如有记录显式为全局指定在查询时,类变量将自动赋值为,添加记录时同理,数据库值将存储为其枚举类实例序号。

场景描述

我们在实际场景中经常会遇到需要将枚举值存储到数据库中,或是将从数据库中查询到的值对应到枚举类上的情况。

比如表process大致定义如下:

-- ----------------------------
-- Table structure for process
-- ----------------------------
DROP TABLE IF EXISTS `process`;
CREATE TABLE `process` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `status` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

对应实体类Process,大致定义如下:

public class Process{
    private int id;
    private String name;
    private ProcessStatus status;
    
    // 省略 getter setter toString 等
}

其中,枚举类ProcessStatus,大致定义如下:

public enum ProcessStatus {
    RUNNING(100, "running"),
    BLOCKED(101, "blocked"),
    STOPPED(102, "stopped");

    private int code;
    private String desc;

    ProcessStatus(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    
    // ...
}

如果此时我们想在存储Process类时直接将ProcessStatus对应成某种值,或者在查询时直接将数据库status字段值对应成为ProcessStatus类,而不需要用硬编码的方式做更多的转换,我们可以考虑采用 MyBatis 提供的typeHandler

MyBatis 内置的枚举处理器

为了处理上述遇到的问题,MyBatis 内置了两种 typeHandler,分别是org.apache.ibatis.type.EnumTypeHandlerorg.apache.ibatis.type.EnumOrdinalTypeHandler

EnumTypeHandler

作为默认的枚举 typeHandler,EnumTypeHandler将使用枚举实例名称来和对应的枚举类之间做转换。

比如process表有记录:

id name status
1 first RUNNING

在查询时,Process类变量status将自动赋值为ProcessStatus.RUNNING,添加记录时同理,数据库值将存储为其枚举类实例名称(RUNNING/BLOCKED/STOPPED)。

EnumOrdinalTypeHandler

EnumOrdinalTypeHandler将使用枚举实例的 ordinal 值(序数值,从0开始)来和枚举类之间做转换。

比如process有记录:

id name status
1 first 1

显式为ProcessStatus全局指定 typeHandler:


    

在查询时,Process类变量status将自动赋值为ProcessStatus.BLOCKED,添加记录时同理,数据库值将存储为其枚举类实例序号(0/1/2)。

混合使用

假如想在一处使用EnumTypeHandler,另外一处使用EnumOrdinalTypeHandler,可以在 mapped statement 中多带带指定 typeHandler。


  insert into process (id, name, status)
  values (#{id}, #{name}, #{status, typeHandler=org.apache.ibatis.type.EnumTypeHandler})


    
    
    

    
自定义枚举处理器

回到我们的场景描述中来,我们需要用枚举实例的 code 值来对应相应的枚举。此时,系统内置的两个枚举处理器便不能很好地完成我们的需求了,所以我们要自定义枚举处理器。

实现方法

枚举处理器也是处理器(typeHandler)的一种,关于自定义处理器的内容,可以参考官方文档。主要操作便是实现org.apache.ibatis.type.TypeHandler或继承更为方便的org.apache.ibatis.type.BaseTypeHandler类。

我们选择继承BaseTypeHandler来完成工作,BaseTypeHandler需要实现4个方法:

public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException

用于定义设置参数时,该如何把 Java 类型的参数转换为对应的数据库类型

public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException

用于定义通过字段名称获取字段数据时,如何把数据库类型转换为对应的 Java 类型

public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException

用于定义通过字段索引获取字段数据时,如何把数据库类型转换为对应的 Java 类型

public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException

用定义调用存储过程后,如何把数据库类型转换为对应的 Java 类型

由于「枚举」是一个统称,不像具体类型的处理器一样可以使用多种方式来指定匹配的 Java 类型,所以按照官方文档的做法,我们将使用指定泛型的方式来自定义枚举构造器(EnumTypeHanlderEnumOrdinalTypeHandler源代码也是这么实现的),官方文档示例:

//GenericTypeHandler.java
public class GenericTypeHandler extends BaseTypeHandler {

    private Class type;

      public GenericTypeHandler(Class type) {
        if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
        this.type = type;
      }
      ...
}
实现过程

为了更好完成自定义枚举的工作,我们修改一下我们上面定义的枚举ProcessStatus,使它实现一个通用接口。

public interface BaseEnum {
    int getCode();
}

public enum ProcessStatus implements BaseEnum{
    RUNNING(100, "running"),
    BLOCKED(101, "blocked"),
    STOPPED(102, "stopped");

    private int code;
    private String desc;

    ProcessStatus(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    
    @Override
    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

然后再使用一个枚举工作类来完成从枚举 code 值获得枚举实例的工作:

public class EnumUtils {

    public static  & BaseEnum> T codeOf(Class enumClass, int code) {
        T[] enumConstants = enumClass.getEnumConstants();
        for (T t : enumConstants) {
            if (t.getCode() == code) {
                return t;
            }
        }
        return null;
    }
}

万事俱备,接下来完成自定义枚举处理器:

import com.foo.BaseEnum;
import com.foo.EnumUtils;
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 EnumCodeTypeHandler & BaseEnum> extends BaseTypeHandler {

    private final Class type;

    public EnumCodeTypeHandler(Class type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
     }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getCode());
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int code = rs.getInt(columnName);
        return rs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int code = rs.getInt(columnIndex);
        return rs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int code = cs.getInt(columnIndex);
        return cs.wasNull() ? null : EnumUtils.codeOf(this.type, code);
    }
}

在 mybatis-config.xml 中注册枚举处理器


    

假设数据库process表有如下记录:

id name status
1 first 101

ProcessMapper.java使用如下查询:

@Select("select * from process where id=#{id}")
public Process findById(int id);

查询结果 status 值将会对应到枚举实例 ProcessStatus.BLOCKED上。

查询结果Process{id=1, name="first", status=BLOCKED}

设置默认枚举处理器

在 mybatis-config.xml 中为单个枚举注册枚举处理器的方式在需要处理的枚举数量增长时,会带来很多不必要的工作量,根据官方文档,我们可以在 configuration - settings节点下设置默认枚举处理器,没有特殊指定处理器的枚举都将默认使用这个处理器。


        
说明和参考资料

说明:文中代码测试基于 JDK 8,MyBatis 3.4.5。

参考资料:

MyBatis官方文档之Configuration

如何在MyBatis中优雅的使用枚举,特别感谢这篇清晰明了的文章。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71158.html

相关文章

  • mybatis处理枚举

    摘要:自带对枚举的处理类该类实现了枚举类型和类型的相互转换。而在具体中也需要使用属性,如在处理到该位置时,就会调用指定的处理类来处理枚举类型。 mybatis自带对枚举的处理类 org.apache.ibatis.type.EnumOrdinalTypeHandler :该类实现了枚举类型和Integer类型的相互转换。 但是给转换仅仅是将对应的枚举转换为其索引位置,也就是ordinal(...

    caspar 评论0 收藏0
  • MyBatis 源码分析系列文章导读

    摘要:本文速览本篇文章是我为接下来的源码分析系列文章写的一个导读文章。年该项目从基金会迁出,并改名为。同期,停止维护。符号所在的行则是表示的执行结果。同时,使用无需处理受检异常,比如。另外,把写在配置文件中,进行集中管理,利于维护。 1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章。本篇文章从 MyBatis 是什么(what),为什么要使用(why),...

    weizx 评论0 收藏0
  • mybatis动态sql

    摘要:枚举类型提供了提供了持久化的枚举类型。假设表结构的列,使用类型存储或,对象使用枚举类型标识当执行语句时,或会存储到列,如果想要存储的时枚举值而不是枚举名字,就需要配置类型处理器和提供了对和的内检支持,将映射为,将映射为数组。 mybatis中静态sql语句有时不足以满足用户的需求,因此其提供了动态sql标签。 IF标签 if标签通过条件测试,动态插入sql片段,例如: an...

    anyway 评论0 收藏0
  • Mybatis学习笔记

    摘要:学习笔记有官方的中文开发文档并且针对使用者比较友好是一款优秀的持久层框架,它支持定制化存储过程以及高级映射。它只和配置有关,存在的意义仅在于用来减少类完全限定名的冗余,为了简化中的书写。 Mybatis学习笔记 mybatis有官方的中文开发文档并且针对使用者比较友好:http://www.mybatis.org/mybatis-3/zh/ MyBatis 是一款优秀的持久层框架,它支...

    jsyzchen 评论0 收藏0
  • Java 最常见 200+ 面试题全解析:面试必备(附答案)

    摘要:的简称,运行环境,为的运行提供了所需环境。分割字符串,返回一个分割后的字符串数组。线程安全是线程安全的,而是非线程安全的。迭代器取代了集合框架中的,迭代器允许调用者在迭代过程中移除元素。 本文分为十九个模块,分别是: Java 基础、容器、多线程、反射、对象拷贝、Java Web 、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Clou...

    hufeng 评论0 收藏0

发表评论

0条评论

Bryan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<