应避免用 ordinal 存数据库状态索引,因其依赖 enum 声明顺序,易因重构、IDE 排序或跨环境版本不一致导致状态错乱;推荐显式定义稳定 code 字段并配合自定义转换器持久化。

别用 ordinal 存数据库状态索引 —— 它脆弱、不可控、一改 enum 顺序就全乱套。
为什么 ordinal 在数据库里是定时炸弹
ordinal 是编译期自动生成的序号,完全依赖 enum 常量声明顺序。一旦你调整顺序(比如把 PENDING 和 APPROVED 对调)、插入新状态到中间、甚至只是重构时 IDE 自动排序了常量,ordinal 就会变,而数据库里存的老数字还指着旧含义。
- 上线后突然发现“订单状态 1”变成“已取消”,其实是原来“待审核”被挤到了 index 2
- 不同环境(开发/测试/生产)enum 文件版本不一致,
ordinal对不上,查不出数据或写错状态 - Java 反编译或 Kotlin 编译器优化可能影响
ordinal行为(虽少见,但非零风险)
真正安全的替代方案:显式定义 code 字段
给每个枚举值绑定一个稳定、语义明确、不随顺序变化的标识符,比如字符串或整数常量。
public enum OrderStatus {
PENDING(10, "pending"),
APPROVED(20, "approved"),
REJECTED(30, "rejected");
private final int code;
private final String value;
OrderStatus(int code, String value) {
this.code = code;
this.value = value;
}
public int getCode() { return code; }
public String getValue() { return value; }
public static OrderStatus fromCode(int code) {
return Arrays.stream(values())
.filter(s -> s.code == code)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown code: " + code));
}
}
- 数据库字段类型用
INT或VARCHAR,存code或value都行,推荐code(数值查询快,且比字符串更紧凑) - 新增状态时,在末尾追加,并手动指定未用过的
code(如 40),不破坏原有映射 - JPA/Hibernate 中用
@Enumerated(EnumType.INTEGER)不适用 —— 改用@Convert自定义转换器,或 MyBatis 用typeHandler
如果非要用整数索引,至少加一层校验
极少数遗留系统已用 ordinal 上线,又不敢立刻改库,可加运行时防护。
- 启动时遍历 enum,检查
ordinal()是否与预期一致:if (OrderStatus.PENDING.ordinal() != 0) throw new IllegalStateException("Enum order changed!") - 在 DAO 层读写前,用
switch显式映射:case 0: return OrderStatus.PENDING;,而不是靠values()[n] - 数据库字段加注释,写明 “此字段对应 OrderStatus#ordinal,严禁调整 enum 声明顺序” —— 但注释拦不住人,只当最后一道心理防线
最麻烦的不是实现,而是团队对 ordinal 的误解:它不是设计用来持久化的。一旦开始依赖它,后续每次 enum 变更都要同步核对数据库、迁移脚本、上下游服务、缓存 key……这些隐性成本远高于一开始多写两行 code 字段。










