Mybatis如何将Integer映射为Enum?

Mybatis how mapping Integer to Enum?

在数据库中,“状态”列是整数。

xmlmybatis

<resultMap id="TaskStatus" type="ru....domain.Task$Status">
            <result typeHandler="org.apache.ibatis.type.EnumTypeHandler"
                    property="id" column="status"/>
</resultMap>
    
<select id="selectStatus" resultMap="TaskStatus">
            select id, status
            from task
            where id = #{id}
</select>

我的枚举class

public class Task{
    
        @Getter
        @AllArgsConstructor
        public enum Status {
            CREATED(1),
            RUNNING(2),
            PAUSED(3),
            FINISHED(4),
            ARCHIVED(5),
            MODERATION_READY(6),
            MODERATING(7),
            REJECTED(8);
    
    
            private final Integer id;
        }
    ....
    }

我想在枚举中添加一列 class。

错误

查询数据库时出错。原因:org.apache.ibatis.executor.result.ResultMapException:尝试从结果集中获取列 'status' 时出错。原因:java.lang.IllegalArgumentException:没有枚举常量 ru...domain.Task.Status.2

默认 EnumTypeHandler 映射枚举的名称(例如 "CREATED""RUNNING"),因此列类型必须是文本类型之一,例如 VARCHAR [1]。

由于 MyBatis 对 id 属性 一无所知,你必须编写自定义类型处理程序。
这是一个示例实现。

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;

@MappedTypes(Status.class)
public class StatusTypeHandler implements TypeHandler<Status> {
  @Override
  public void setParameter(PreparedStatement ps, 
       int i, Status parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      ps.setNull(i, Types.INTEGER);
    } else {
      ps.setInt(i, parameter.getId());
    }
  }

  @Override
  public Status getResult(ResultSet rs, String columnName) throws SQLException {
    return getStatus(rs.getInt(columnName));
  }

  @Override
  public Status getResult(ResultSet rs, int columnIndex) throws SQLException {
    return getStatus(rs.getInt(columnIndex));
  }

  @Override
  public Status getResult(CallableStatement cs, int columnIndex) throws SQLException {
    return getStatus(cs.getInt(columnIndex));
  }

  private static Status getStatus(int id) {
    if (id == 0) {
      return null;
    }
    for (Status status : Status.values()) {
      if (id == status.getId()) {
        return status;
      }
    }
    throw new IllegalArgumentException("Cannot convert " + id + " to Status");
  }
}

您应该全局注册此类型处理程序。
那么在大多数情况下不需要明确指定typeHandler

  1. 如果您使用 mybatis-spring-boot,在配置中指定 mybatis.type-handlers-package 可能是全局注册类型处理程序的最简单方法。

  2. 如果您使用 XML 配置,请添加以下内容。

    <typeHandlers>
      <typeHandler
        handler="xxx.yyy.StatusTypeHandler" />
    </typeHandlers>
    

如果 Status 是您项目中唯一的枚举,您可以停止阅读。

但是,如果有许多具有 id 属性 的枚举并且您不想为每个枚举编写类似的自定义类型处理程序怎么办?

如果您的枚举实现了如下所示的通用接口,您可以编写一个可以映射所有枚举的类型处理程序。

public interface HasId {
  Integer getId();
}

这是一个示例类型处理程序实现。 请注意,它有一个以 java.lang.Class 作为参数的构造函数。

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;

@MappedTypes(HasId.class)
public class HasIdTypeHandler<E extends Enum<E> & HasId> implements TypeHandler<E> {
  private Class<E> type;
  private final E[] enums;

  public HasIdTypeHandler(Class<E> type) {
    if (type == null)
      throw new IllegalArgumentException("Type argument cannot be null");
    this.type = type;
    this.enums = type.getEnumConstants();
    if (!type.isInterface() && this.enums == null)
      throw new IllegalArgumentException(type.getSimpleName()
          + " does not represent an enum type.");
  }

  @Override
  public void setParameter(PreparedStatement ps, 
      int i, E parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      ps.setNull(i, Types.INTEGER);
    } else {
      ps.setInt(i, parameter.getId());
    }
  }

  @Override
  public E getResult(ResultSet rs, String columnName) throws SQLException {
    return getEnum(rs.getInt(columnName));
  }

  @Override
  public E getResult(ResultSet rs, int columnIndex) throws SQLException {
    return getEnum(rs.getInt(columnIndex));
  }

  @Override
  public E getResult(CallableStatement cs, int columnIndex) throws SQLException {
    return getEnum(cs.getInt(columnIndex));
  }

  private E getEnum(int id) {
    if (id == 0) {
      return null;
    }
    for (E e : enums) {
      if (id == e.getId()) {
        return e;
      }
    }
    throw new IllegalArgumentException("Cannot convert " +
      id + " to " + type.getSimpleName());
  }
}

请注意,如果您尝试在映射器中指定此类型处理程序,它可能无法正常工作。有一个已知的 issue.

[1] 仅供参考,枚举还有另一个 built-in 类型处理程序:EnumOrdinalTypeHandler 映射枚举的 ordinal.