如何在CRUD时自动完成枚举和数值的类型转换

  • 在设计业务数据库的时候,为了减少存储,通常会将一些可列举值的字段,设置成数字(tinyint)型,比如应用的角色(学生、教师),性别(男,女)等
  • 在Java代码中,使用枚举类型(enum)来声明这些数据域,这就涉及到了一个转换的过程: 插入数据时把枚举类型转换数字,而读取数据时把数字转换成枚举类型

最简单的方法当然是手动写每一个域的转换,但是这么一来,随着Bean的数量和枚举的properties的数量的增加,重复的手工作业会越来越繁琐,代码也会越看越不舒服.

重复的代码越多,就越要重构成最好能自动实现的转换功能,思路上来说先是在ORM的框架上寻找办法,后来因为机缘巧合认识了一个第三方库Orika,最终的版本是使用这个库来实现的.

通过MyBatis实现枚举类型和数值的转换

MyBatis中实现转换,需要通过typeHandlers. 我的实现借用了CSDN上的这篇文章: mybatis处理enum类型的实现方式。

因为在创建枚举类时,一般都会加上自己的数字索引,我的枚举类如下:

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);
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值