MyBatis枚举类型的使用方法

18

我知道这个问题以前已经被问过,但是根据我找到的信息,我无法实现一个解决方案。也许有人可以向我解释一下。

我有一张名为“status”的表。它有两列:id和name。id是主键。

我想使用枚举来代替POJO Status。我创建了如下的枚举:

public enum Status {
    NEW(1), READY(2), CLOSED(3);

    private int id;

    public void setId(int id) {
        this.id = id;
    }

    public int getId() {
        return this.id;
    }

    Status(int id) {
        this.id = id;
    }
}

这是我的映射器

     <select id="getStatusByName" resultType="Status" parameterType="String">       
        SELECT  ls.id, ls.name
        FROM status AS ls
        WHERE ls.name = #{name}
    </select>

但是出于某些原因,当我尝试检索枚举时,会发生故障,但没有抛出任何异常。


3
我认为在枚举(enum)中使用setId()是一个可怕的想法... :) - Doctor Eval
2个回答

17

我从不同角度研究了这个问题,以下是我的发现。注:我使用的是 MyBatis-3.1.1 进行所有调查,因此在早期版本中可能会有所不同。

首先,MyBatis 有一个内置的 EnumTypeHandler。默认情况下,每当您指定 Java 枚举作为 resultType 或 parameterType 时,它将处理该类型。对于查询,在尝试将数据库记录转换为 Java 枚举时,EnumTypeHandler 只需要一个参数,并尝试查找对应于该值的 Java 枚举值。

举个例子来说明。假设你的查询返回 2"Ready",而我将 "Ready" 作为参数传递进去。在这种情况下,我会得到错误消息 No enum constant com.foo.Status.2。如果我反转您的 SELECT 语句的顺序,则会获得正确的结果。

SELECT ls.name, ls.id

如果出现错误,错误信息将是No enum constant com.foo.Status.Ready。我假设您可以推断出MyBatis在做什么。请注意,EnumTypeHandler会忽略查询返回的第二个值。

将您的查询更改为

SELECT UPPER(ls.name)

如果需要让Status.READY枚举类型正常工作,则须用EnumTypeHandler来进行处理。接下来我尝试自定义一个Status枚举类型的TypeHandler,但与默认的EnumTypeHandler一样,只能获取到枚举id或name中的一个值以引用正确的枚举,无法同时获取两个值。所以,如果数据库中的id与上述硬编码的值不匹配,则会出现不匹配情况。如果确保数据库id始终与枚举中指定的id匹配,则只需从数据库获取名称(转换为大写)即可。

随后,我想聪明地实现一个MyBatis ObjectFactory,获取int id和String name,确保它们在传递回去的Java枚举中相互匹配,但是这种方法行不通,因为MyBatis不会调用Java枚举类型的ObjectFactory(至少我不能使其工作)。

因此,我的结论是,只要需要将数据库中的名称与枚举常量名称匹配即可,Java枚举在MyBatis中就很容易处理——可以使用内置的EnumTypeHandler,或者如果在SQL中使用UPPER(name)无法匹配Java枚举名称,则可以自定义TypeHandler。在许多情况下,这已经足够了,因为枚举值可能只是列上的检查约束,并且只有单个值,而不是一个id。如果还需要匹配int id和名称,则需要在设置Java枚举和/或数据库条目时手动匹配这些ID。

最后,如果您想看到一个工作示例,请参见我的MyBatis koans的第23个案例:https://github.com/midpeter444/mybatis-koans。如果您只想看我的解决方案,请查看completed-koans / koan23目录。我还在那里举了一个使用Java枚举向数据库插入记录的例子。


我认为这是一种变通的方法,faizian的答案使用自定义TypeHandler是更干净的解决方案。 - Víťa Plšek - angular.cz

13

您可以使用自定义 TypeHandler 将结果直接转换为 ENUM,这样您就不需要在数据库中将所有值作为大写 ENUM 名称存储。

这是您的状态枚举自定义处理程序的外观。

public class StatusTypeHandler implements TypeHandler<Status> {

public Status getResult(ResultSet rs, String param) throws SQLException {
    return Status.getEnum(rs.getInt(param));
}

public Status getResult(CallableStatement cs, int col) throws SQLException {
    return Status.getEnum(cs.getInt(col));
}

public void setParameter(PreparedStatement ps, int paramInt, Status paramType, JdbcType jdbctype)
        throws SQLException {
    ps.setInt(paramInt, paramType.getId());
}
}

通过添加以下代码在 mybatis-config.xml 中定义 TypeHandler,以默认方式处理 Status。

  <typeHandlers>
    <typeHandler javaType="com.example.Status" handler="com.example.MyTypeHandler" />
  </typeHandlers>
    <typeHandlers> 
            <typeHandler javaType='Status' handler='StatusTypeHandler' /> 
    </typeHandlers>

现在让我们考虑一个例子,假设您的Dao中有以下两个函数:

Status getStatusById(int code);
Status getStatusByName(String name);

你的映射器将会长成这个样子

<select id="getStatusById" resultType="Status" parameterType="int">       
    SELECT  ls.id
    FROM status AS ls
    WHERE ls.id = #{id}
</select>

<select id="getStatusByName" resultType="Status" parameterType="String">       
    SELECT  ls.id
    FROM status AS ls
    WHERE ls.name = #{name}
</select>

现在由于两个mapper的resultType都是Status,因此myBatis将使用CustomTypeHandler来处理这种类型,即StatusTypeHandler,而不是默认情况下用于处理枚举的EnumTypeHandler,因此您无需在数据库中维护正确的枚举名称。


如果我需要在不传递参数(如id、name等)的情况下返回枚举值,该怎么办? - axcdnt
6
那是一个提供干净解决方案的答案。依靠大写字母看起来很丑陋。 - Eduardo
2
我没有mybatis-config.xml文件,因为使用了Spring Boot。我应该将xml代码添加到哪个文件中? - Wolf359

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接