Skip to main content

如何优雅的使用枚举[1]

springbootmybatistype handlerAbout 4 min

这篇文章从数据库交互方面介绍枚举的使用方法.

表结构

数据库表中, 像状态,类型这样含义的字段通常都是可以用枚举进行替代的. 如下面的表结构中artical_type

CREATE TABLE `blog_artical` (
    `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '文章id',
    `author_id` INT(11) UNSIGNED NOT NULL COMMENT '作者id',
    `title` VARCHAR(50) NULL DEFAULT NULL COMMENT '文章标题',
    `content` TEXT NULL COMMENT '文章内容',
    `description` VARCHAR(200) NULL DEFAULT NULL COMMENT '描述',
    `artical_type` TINYINT(4) UNSIGNED NOT NULL DEFAULT '3' COMMENT '文章类型 1.技术2.生活3.草稿4.其他',
    `is_delete` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否删除',
    `create_time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `update_time` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (`id`),
    INDEX `idx_author_id` (`author_id`)
)
COLLATE='utf8mb4_0900_ai_ci'
ENGINE=InnoDB
;

创建枚举类

为了和数据库中artical_type对应, 需要创建一个枚举类. 注意这里我们继承并重写了getCode方法, 其目的在于统一获取枚举code的方式. ArticalTypeEnum使用getCode, 如果其他枚举使用getValue,getInt等等, 将十分混乱.

public enum  ArticalTypeEnum implements BaseCode {
    /** 技术 */
    TECH(1, "技术"),
    /** 生活 */
    LIFE(2, "生活"),
    /** 草稿 */
    DRAFT(3, "草稿"),
    /** 其他 */
    OTHER(4, "其他");

    private Integer code;
    private String description;

    ArticalTypeEnum(Integer code, String description) {
        this.code = code;
        this.description = description;
    }

    @Override
    public Integer getCode() {
        return this.code;
    }

    public String getDescription() {
        return description;
    }
}


/**
 * 为了统一数据库和对应枚举值的对应关系, 接口中的getCode方法为获取枚举值的方式
 */
public interface BaseCode {

    Integer getCode();

    /**
     * 根据枚举类型和code获取对应的枚举
     * @param c
     * @param code
     * @param <T>
     * @return
     */
    static <T extends BaseCode> T valueOfEnum(Class<T> c, int code) {
        BaseCode[] enums = c.getEnumConstants();
        Optional<BaseCode> optional = Arrays.asList(enums).stream()
                .filter(baseEnum -> baseEnum.getCode().equals(code)).findAny();
        if (optional.isPresent()) {
            return (T) optional.get();
        }
        throw new IllegalArgumentException(String.format("[%s]没有对应枚举值: [%s]", c.getName(), code));
    }

}

定义枚举转换规则

实现自定义的转换规则只需要继承BaseTypeHandler重写方法即可

public class BaseCodeTypeHandler<E extends BaseCode> extends BaseTypeHandler<E> {

    private final Class<E> type;

    public BaseCodeTypeHandler(Class<E> type) {

        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        } else {
            this.type = type;
        }
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getCode());
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int code = rs.getInt(columnName);
        return rs.wasNull() ? null : BaseCode.valueOfEnum(this.type, code);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int code = rs.getInt(columnIndex);
        return rs.wasNull() ? null : BaseCode.valueOfEnum(this.type, code);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int code = cs.getInt(columnIndex);
        return cs.wasNull() ? null : BaseCode.valueOfEnum(this.type, code);
    }

}

我们可以继续封装一个自动转换枚举类型的转换器, 其作用为如果实现了自定义枚举类通过自定义枚举类进行转换, 否则使用默认的EnumTypeHandler进行转换. 注意这里面的复写方法只是使用对应的转换器去调用相应的方法. 因此注册转换器的时候只要注册AutoEnumTypeHandler就可以.

public class AutoEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {

    private BaseTypeHandler typeHandler = null;

    public AutoEnumTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        if(BaseCode.class.isAssignableFrom(type)){
            typeHandler = new BaseCodeTypeHandler(type);
        }else {
            typeHandler = new EnumTypeHandler<>(type);
        }
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        typeHandler.setNonNullParameter(ps,i, parameter,jdbcType);
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return (E) typeHandler.getNullableResult(rs,columnName);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return (E) typeHandler.getNullableResult(rs,columnIndex);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return (E) typeHandler.getNullableResult(cs,columnIndex);
    }
}

注册枚举转换器

SqlSessionFactory sqlSessionFactory = bean.getObject();
TypeHandlerRegistry typeHandlerRegistry = sqlSessionFactory.getConfiguration().getTypeHandlerRegistry();
typeHandlerRegistry.setDefaultEnumTypeHandler(AutoEnumTypeHandler.class);

mybatisGenerator

注意mybatisGenerator.xml在逆向工程中可以直接帮我们生成数据库对应的实体类, 因为我们需要类型字段为枚举因此需要在生成的时候指定字段类型以及转换器.

 <table tableName="blog_artical">
            <ignoreColumn column="create_time"/>
            <ignoreColumn column="update_time"/>
            <columnOverride column="artical_type"
                                javaType="com.cxqy.community.constant.ArticalTypeEnum"
                                jdbcType="TINYINT"
                                typeHandler="com.cxqy.community.util.handler.AutoEnumTypeHandler"/>
            <columnOverride column="is_delete"
                            javaType="java.lang.Boolean"
                            jdbcType="TINYINT"
                            typeHandler="com.cxqy.community.util.handler.AutoEnumTypeHandler"/>

生成的实体类中的类型字段:

2020-01-30-10-22-47

测试

首先向数据库插入一条信息, 之后进行查询, 并获取文章类型.

@Test
public void getValue() {
    BlogArtical artical = new BlogArtical();
    artical.setAuthorId(1);
    artical.setTitle("标题");
    artical.setDescription("描述");
    artical.setArticalType(ArticalTypeEnum.LIFE);
    artical.setIsDelete(false);
    artical.setContent("文本内容");
    blogArticalMapper.insertUseGeneratedKeys(artical);

    BlogArtical articalQueried = blogArticalMapper.selectByPrimaryKey(artical.getId());
    log.debug("输出文章类型信息: {}", articalQueried.getArticalType());
}

运行结果:

2020-01-30-10-14-46

通过这种方式, 我们的代码里将减少大量和数据库中类型有关的魔法值.

What do you think?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v3.0.0-alpha.10