使用MyBatis簡化枚舉類值的存儲和讀取

場景描述

咱們在實際場景中常常會遇到須要將枚舉值存儲到數據庫中,或是將從數據庫中查詢到的值對應到枚舉類上的狀況。html

好比表process大體定義以下:java

-- ----------------------------
-- 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,大體定義以下:mysql

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

其中,枚舉類ProcessStatus,大體定義以下:sql

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.EnumOrdinalTypeHandlerapache

EnumTypeHandler

做爲默認的枚舉 typeHandler,EnumTypeHandler將使用枚舉實例名稱來和對應的枚舉類之間作轉換。segmentfault

好比process表有記錄:mybatis

id name status
1 first RUNNING

在查詢時,Process類變量status將自動賦值爲ProcessStatus.RUNNING,添加記錄時同理,數據庫值將存儲爲其枚舉類實例名稱(RUNNING/BLOCKED/STOPPED)。app

EnumOrdinalTypeHandler

EnumOrdinalTypeHandler將使用枚舉實例的 ordinal 值(序數值,從0開始)來和枚舉類之間作轉換。ide

好比process有記錄:

id name status
1 first 1

顯式爲ProcessStatus全局指定 typeHandler:

<typeHandlers>
    <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.foo.ProcessStatus"/>
</typeHandlers>

在查詢時,Process類變量status將自動賦值爲ProcessStatus.BLOCKED,添加記錄時同理,數據庫值將存儲爲其枚舉類實例序號(0/1/2)。

混合使用

假如想在一處使用EnumTypeHandler,另一處使用EnumOrdinalTypeHandler,能夠在 mapped statement 中單獨指定 typeHandler。

<insert id="insert" parameterType="com.foo.Process">
  insert into process (id, name, status)
  values (#{id}, #{name}, #{status, typeHandler=org.apache.ibatis.type.EnumTypeHandler})
</insert>

<resultMap id="processMap" type="com.foo.Process">
    <id column="id" property="id"/>
    <id column="name" property="name"/>
    <id column="status" property="status" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
</resultMap>
    
<select id="findById" resultMap="processMap">
    select * FROM process WHERE id=#{id}
</select>

自定義枚舉處理器

回到咱們的場景描述中來,咱們須要用枚舉實例的 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<E extends MyObject> extends BaseTypeHandler<E> {

    private Class<E> type;

      public GenericTypeHandler(Class<E> 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 <T extends Enum<?> & BaseEnum> T codeOf(Class<T> 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<E extends Enum<E> & BaseEnum> extends BaseTypeHandler<E> {

    private final Class<E> type;

    public EnumCodeTypeHandler(Class<E> 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 中註冊枚舉處理器

<typeHandlers>
    <typeHandler handler="com.foo.EnumCodeTypeHandler" javaType="com.foo.ProcessStatus" />
</typeHandlers>

假設數據庫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節點下設置默認枚舉處理器,沒有特殊指定處理器的枚舉都將默認使用這個處理器。

<settings>
    <setting name="defaultEnumTypeHandler" value="com.foo.EnumCodeTypeHandler" />    
</settings>

說明和參考資料

說明:文中代碼測試基於 JDK 8,MyBatis 3.4.5。

參考資料:

MyBatis官方文檔之Configuration

如何在MyBatis中優雅的使用枚舉,特別感謝這篇清晰明瞭的文章。

相關文章
相關標籤/搜索