如何在CRUD时自动完成枚举和数值的类型转换
最简单的方法当然是手动写每一个域的转换,但是这么一来,随着Bean的数量和枚举的properties的数量的增加,重复的手工作业会越来越繁琐,代码也会越看越不舒服.
重复的代码越多,就越要重构成最好能自动实现的转换功能,思路上来说先是在ORM的框架上寻找办法,后来因为机缘巧合认识了一个第三方库Orika,最终的版本是使用这个库来实现的.
通过MyBatis实现枚举类型和数值的转换
因为在创建枚举类时,一般都会加上自己的数字索引,我的枚举类如下:
public enum DatasourceType implements IntEnum<DatasourceType> {
MYSQL(1), HBASE(2);
@Override
public int getIntValue() {
return this.index;
}
}
这里的接口IntEnum
定义如下:
public interface IntEnum<E extends Enum<E>> {
int getIntValue();
}
处理类可以写的更加通用:
public class IntEnumTypeHandler<E extends Enum<E> & IntEnum<E>> extends BaseTypeHandler<IntEnum> {
private Class<IntEnum> type;
public IntEnumTypeHandler(Class<IntEnum> type) {
if (type == null){ throw new IllegalArgumentException("Type argument cannot be null");}
this.type = type;
}
private IntEnum convert(int status) {
IntEnum[] objs = type.getEnumConstants();
for (IntEnum em : objs) {
if (em.getIntValue() == status) {
return em;
}
}
return null;
}
@Override
public IntEnum getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return convert(rs.getInt(columnName));
}
@Override
public IntEnum getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return convert(rs.getInt(columnIndex));
}
@Override
public IntEnum getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return convert(cs.getInt(columnIndex));
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
IntEnum enumObj, JdbcType jdbcType) throws SQLException {
// baseTypeHandler已经帮我们做了parameter的null判断
ps.setInt(i, enumObj.getIntValue());
}
}
在SQL映射文件中,相应的列需要加上<typeHandlers>
的配置,这里我省略了不相干的其他字段:
<resultMap id="VisualTable" type="com.xxxxx.VisualTable">
<result column="datasource_type" property="datasource"
typeHandler="com.nd.oneservice.util.IntEnumTypeHandler"/>
</resultMap>
<insert id="insert" parameterType="com.xxxxx.VisualTable" statementType="PREPARED">
INSERT INTO T_VisualTable(datasource_type)
VALUES (#{datasource, typeHandler=com.nd.oneservice.util.IntEnumTypeHandler}
</insert>
通过Hibernate实现枚举类型和数值的转换
相比于MyBatis,Hibernate实现这个转换就简单很多,主要方式是通过实现AttributeConverter
接口.
参考MyBatis实现时处理器的通用性,我也对枚举类型做了一个接口抽象:
public interface CommonEnum {
Map<Integer, CommonEnum> enumMap = Maps.newHashMap();
Integer getValue();
static CommonEnum fromInteger(Integer value) {
return enumMap.get(value);
}
}
转换器的实现如下:
public class EnumConverterKit implements AttributeConverter<CommonEnum, Integer> {
@Override
public Integer convertToDatabaseColumn(CommonEnum s) {
return s.getValue();
}
@Override
public CommonEnum convertToEntityAttribute(Integer value) {
return CommonEnum.fromInteger(value);
}
}
这样每个枚举类型,都实现这个CommonEnum接口,OverridegetValue()
这个方法即可
public enum UserRole implements CommonEnum {
ADMIN("ADMIN", 0);
private String name;
private Integer value;
@Override
public Integer getValue() {
return this.value;
}
}
用Hibernate开发CRUD的便捷性在于不用自己写配置文件来拼接SQL,所以到这里就可以了。
最终选择: Orika实现枚举类型和数值的转换
Orika这个库,有点像Lombok一样,大大解放了生产力,不用手动写繁杂的Get和Set。
之所以把Enum和Integer的转换,从ORM框架中迁移过来,因为看着Hibernate实现的不太优美,MyBatiis又写得过于复杂,而且功能的实现和ORM框架绑定,如果不同的项目用不同的ORM框架,就得去做不同的实现,这就显得不那么通用了,
Orika的实现,同样先定义一个通用枚举接口:
public interface CommonEnum {
Integer getValue();
static <T extends CommonEnum> T fromValue(Type<T> enumType, Integer value) {
for (T object : enumType.getRawType().getEnumConstants()) {
if (Objects.equals(value, object.getValue()))
return object;
}
return null;
}
}
转换器的实现如下, 这里继承的是Orika的双向转换类:
public class EnumConverter<E extends CommonEnum> extends BidirectionalConverter<E, Integer> {
@Override
public Integer convertTo(E e, Type<Integer> type, MappingContext mappingContext) {
return e.getValue();
}
@Override
public E convertFrom(Integer integer, Type<E> type, MappingContext mappingContext) {
return CommonEnum.fromValue(type, integer);
}
}
枚举类型定义:
public enum UserRole implements CommonEnum {
ADMIN(1);
private int value;
UserRole(int value) {
this.value = value;
}
@Override
public Integer getValue() {
return this.value;
}
}
在Service层完成转换的步骤就很简单了,只要把转换器注册上就OK了:
private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder()
.mapNulls(false)
.build();
static {
ConverterFactory converterFactory = mapperFactory.getConverterFactory();
converterFactory.registerConverter("enum-integer-converter", new EnumConverter<>());
mapperFactory.classMap(User.class, UserPO.class)
.fieldMap("role", "role").converter("enum-integer-converter").add()
.byDefault()
.register();
}
插入和查找数据的时候,枚举类型和数值就自动完成了转换:
public User addUser(User user) {
UserPO userPO = mapperFactory.getMapperFacade().map(user, UserPO.class);
userDao.save(userPO);
user.setId(userPO.getId());
return user;
}
public User getUser(Long id) {
UserPO userPO = userDao.findById(id)
.orElseThrow(() -> new CommonApiException("user.not.found"));
return mapperFactory.getMapperFacade().map(userPO, User.class);
}
因篇幅问题不能全部显示,请点此查看更多更全内容