Как указать typeHandler для Set <MyEnum>в mybatis? - PullRequest
0 голосов
/ 14 июня 2019

Мне нужно сериализовать и десериализовать Set<MyEnum>, используя mybatis. Хорошо работает при сериализации, но получает Set<String> при десериализации. Вот мой код:
mybatis-config.xml:

<typeHandlers>
        <typeHandler handler="com.my.JsonTypeHandler" javaType="com.my.MyEnum"/>
        <typeHandler handler="com.my.JsonTypeHandler" javaType="java.util.Set"/>
</typeHandlers>

MyEnum:

@Getter
@AllArgsConstructor
public enum MyEnum {

    MILEAGE(1),
    DRIVE_DURATION(2)
    ;

    private int value;

    @JsonCreator
    public static MyEnum fromName(String name) {
        return MyEnum.valueOf(name);
    }
}

mapper.xml:

<select id="queryByParam" parameterType="com.my.DaoQueryParam" resultType="com.my.model.Product">
        select * from product
        <where>
            product_id = #{assetProductId}
        </where>
</select>
<insert id="insert" parameterType="com.my.model.Product">
    insert into product
    (some code here)
</insert>

Продукт:

@Data // lombok
public class Product {

    private Set<MyEnum> myEnums;
}

JsonTypeHandler:

public class JsonTypeHandler<T extends Object> extends BaseTypeHandler<T> {
    private static final ObjectMapper mapper = new ObjectMapper()
            .setSerializationInclusion(JsonInclude.Include.NON_NULL);

    private Class<T> clazz;

    public JsonTypeHandler(Class<T> clazz) {
        if (clazz == null) throw new IllegalArgumentException("Type argument cannot be null");
        this.clazz = clazz;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, this.toJson(parameter));
    }

    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return this.toObject(rs.getString(columnName), clazz);
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return this.toObject(rs.getString(columnIndex), clazz);
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return this.toObject(cs.getString(columnIndex), clazz);
    }

    private String toJson(T object) {
        try {
            return mapper.writeValueAsString(object);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private T toObject(String content, Class<?> clazz) {
        if (content != null && !content.isEmpty()) {
            try {
                return (T) mapper.readValue(content, clazz);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else {
            return null;
        }
    }

}

соответствующий тип столбца myEnums в mysql - VARCHAR. При сериализации я получаю удовлетворительный результат, например. ["MILEAGE"]. при десериализации не выдает никакой ошибки, но поле myEnums в Product оказывается на самом деле Set<String>. Я пытался изменить mybatis-config.xml:

<typeHandlers>
        <typeHandler handler="com.my.JsonTypeHandler" javaType="java.util.Set<MyEnum>"/>
</typeHandlers>

но произошла ошибка и сказано, что символ <не допускается в javaType. Я новичок в mybatis, и эта проблема беспокоила меня часами. Кто-нибудь может помочь? </p>

1 Ответ

0 голосов
/ 15 июня 2019

Лучшее, что вы можете сделать, я думаю, это написать JsonSetTypeHandler.


import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Set;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonSetTypeHandler extends BaseTypeHandler<Set<?>> {
  private static final ObjectMapper mapper = new ObjectMapper()
      .setSerializationInclusion(JsonInclude.Include.NON_NULL);

  private final JavaType javaType;

  public JsonSetTypeHandler(Class<?> clazz) {
    if (clazz == null)
      throw new IllegalArgumentException("Type argument cannot be null");
    this.javaType = mapper.getTypeFactory()
      .constructCollectionType(Set.class, clazz);
  }

  @Override
  public void setNonNullParameter(PreparedStatement ps,
      int i, Set<?> parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setString(i, this.toJson(parameter));
  }

  @Override
  public Set<?> getNullableResult(ResultSet rs,
      String columnName) throws SQLException {
    return this.toSet(rs.getString(columnName));
  }

  @Override
  public Set<?> getNullableResult(ResultSet rs,
      int columnIndex) throws SQLException {
    return this.toSet(rs.getString(columnIndex));
  }

  @Override
  public Set<?> getNullableResult(CallableStatement cs,
      int columnIndex) throws SQLException {
    return this.toSet(cs.getString(columnIndex));
  }

  private String toJson(Object object) {
    try {
      return mapper.writeValueAsString(object);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  private Set<?> toSet(String content) {
    if (content != null && !content.isEmpty()) {
      try {
        return mapper.readValue(content, javaType);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    } else {
      return null;
    }
  }
}

И вам может потребоваться указать typeHandler для каждого сопоставления параметра / результата вместо глобальной регистрации обработчика типа [1].

<resultMap type="com.my.model.Product" id="productRM">
  <id property="id" column="id" />
  <result property="myEnums" column="my_enums"
    typeHandler="com.my.JsonSetTypeHandler" javaType="com.my.MyEnum" />
</resultMap>

<select id="queryByParam" resultMap="productRM">
  select * from product where ...
</select>

<insert id="insert">
  insert into product (id, my_enums) values (#{id},
  #{myEnums,typeHandler=com.my.JsonSetTypeHandler,javaType=com.my.MyEnum})
</insert>
...