对于数据字典型字段,java的枚举比起Integer好处多多,比如

1、限定值,只能赋值枚举的那几个实例,不能像Integer随便输,保存和查询的时候特别有用

2、含义明确,使用时不需要去查数据字典

3、显示值跟存储值直接映射,不需要手动转换,比如1在页面上显示为启用,0显示禁用,枚举定义好可以直接显示

4、基于enum可以添加一些拓展方法

我的项目使用spring boot JPA(hibernate实现),支持@Enumerated的annotation来标注字段类型为枚举,如:

@Enumerated(EnumType.ORDINAL)
@Column(name = "STATUS")
private StatusEnum status;

Enumerated提供了两种持久化枚举的方式,EnumType.ORDINAL和EnumType.STRING,但都有很大的局限性,让人很难选择,经常不能满足需求

EnumType.ORDINAL:按枚举的顺序保存数字

有一些我项目不能容忍的局限性,比如

1、顺序性 - java枚举的顺序从0开始递增,没法自己指定,我有些枚举并不是从0开始的,或者不是+1递增的,比如一些行业的标准代码。

2、旧数据可能不兼容,比如-1代表删除,映射不了

3、不健壮 - 项目那么多人开发,保不准一个猪队友往枚举中间加了一个值,那完了,数据库里的记录就要对不上了。数据错误没有异常,发现和排查比较困难

EnumType.STRING:保存枚举的值,也就是toString()的值

同样有局限性:

1、String类型,数据库定义的是int,即使override toString方法返回数字的String,JPA也保存不了

2、同样不适用旧数据,旧数据是int

3、不能改名,改了后数据库的记录映射不了

我对枚举需求其实很简单,1是保存int型,2是值可以自己指定,可惜默认的那两种都实现不了。

没办法,只能考虑在保存和取出的时候自己转换了,然后很容易就找到实体转换器AttributeConverter,可以自定义保存好取出时的数据转换,Yeah!(似乎)完美解决问题!

实现如下:

定义枚举

public enum StatusEnum {Deleted(-1, "删除"),Inactive(0, "禁用"),Active(1, "启用");private Integer value;private String display;private StatusEnum(int value, String display) {this.value = value;this.display = display;}//显示名public String getDisplay() {return display;}//保存值public Integer getValue() {return value;}//获取枚举实例public static StatusEnum fromValue(Integer value) {for (StatusEnum statusEnum : StatusEnum.values()) {if (Objects.equals(value, statusEnum.getValue())) {return statusEnum;}}throw new IllegalArgumentException();}
}

创建Convert,很简单,就是枚举跟枚举值的转换

public class EnumConvert implements AttributeConverter<StatusEnum, Integer> {@Overridepublic Integer convertToDatabaseColumn(StatusEnum attribute) {return attribute.getValue();}@Overridepublic StatusEnum convertToEntityAttribute(Integer dbData) {return StatusEnum.fromValue(dbData);}
}

网上说class上加上@Converter(autoApply = true),JPA能自动识别类型并转换,然而我用spring boot跑unit test实验了并不起作用,使用还是把@Converter加在实体字段上

    @Convert(converter = EnumConvert.class)@Column(name = "STATUS")private StatusEnum status;

嗯,测试结果正常,很好!

等等,,我有20个左右的枚举,难道我要建20个转换器??咱程序猿怎么能干这种搬砖的活呢?必须简化!

我试试用泛型,先定义一个枚举的接口

public interface IBaseDbEnum {/*** 用于显示的枚举名** @return*/String getDisplay();/*** 存储到数据库的枚举值** @return*/Integer getValue();//按枚举的value获取枚举实例static <T extends IBaseDbEnum> T fromValue(Class<T> enumType, Integer value) {for (T object : enumType.getEnumConstants()) {if (Objects.equals(value, object.getValue())) {return object;}}throw new IllegalArgumentException("No enum value " + value + " of " + enumType.getCanonicalName());}
}

然后Convert改为泛型

public class EnumConvert<T extends IBaseDbEnum> implements AttributeConverter<T, Integer> {@Overridepublic Integer convertToDatabaseColumn(T attribute) {return attribute.getValue();}@Overridepublic T convertToEntityAttribute(Integer dbData) {//先随便写,测试一下return (T) StatusEnum.Active;}
}

可是到这犯难了,实体的@Convert怎么写呢?converter参数要求class类型,@Convert(converter = EnumConvert<StatusEnum>.class)这种写法不能通过啊,不传入泛型参数,又没办法吧数据库的int转换为具体枚举,这不还是要写20多个转换器?继承泛型的基类转换器只是减少了一部分代码而已,还是不能接受。

Convert方式走不通,然后考虑其他方式,干脆把枚举当做一个自定义类型,不用局限于枚举身上,只要能实现保存和映射就足够了。

创建自定义的UserType - DbEnumType,完整代码如下:

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.DynamicParameterizedType;
import org.hibernate.usertype.UserType;import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Objects;
import java.util.Properties;/*** 数据库枚举类型映射* 枚举保存到数据库的是枚举的.getValue()的值,为Integer类型,数据库返回对象时需要把Integer转换枚举* Create by XiaoQ on 2017-11-22.*/
public class DbEnumType implements UserType, DynamicParameterizedType {private Class enumClass;private static final int[] SQL_TYPES = new int[]{Types.INTEGER};@Overridepublic void setParameterValues(Properties parameters) {final ParameterType reader = (ParameterType) parameters.get(PARAMETER_TYPE);if (reader != null) {enumClass = reader.getReturnedClass().asSubclass(Enum.class);}}//枚举存储int值@Overridepublic int[] sqlTypes() {return SQL_TYPES;}@Overridepublic Class returnedClass() {return enumClass;}//是否相等,不相等会触发JPA update操作@Overridepublic boolean equals(Object x, Object y) throws HibernateException {if (x == null && y == null) {return true;}if ((x == null && y != null) || (x != null && y == null)) {return false;}return x.equals(y);}@Overridepublic int hashCode(Object x) throws HibernateException {return x == null ? 0 : x.hashCode();}//返回枚举@Overridepublic Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {String value = rs.getString(names[0]);if (value == null) {return null;}for (Object object : enumClass.getEnumConstants()) {if (Objects.equals(Integer.parseInt(value), ((IBaseDbEnum) object).getValue())) {return object;}}throw new RuntimeException(String.format("Unknown name value [%s] for enum class [%s]", value, enumClass.getName()));}//保存枚举值@Overridepublic void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {if (value == null) {st.setNull(index, SQL_TYPES[0]);} else if (value instanceof Integer) {st.setInt(index, (Integer) value);} else {st.setInt(index, ((IBaseDbEnum) value).getValue());}}@Overridepublic Object deepCopy(Object value) throws HibernateException {return value;}@Overridepublic boolean isMutable() {return false;}@Overridepublic Serializable disassemble(Object value) throws HibernateException {return (Serializable) value;}@Overridepublic Object assemble(Serializable cached, Object owner) throws HibernateException {return cached;}@Overridepublic Object replace(Object original, Object target, Object owner) throws HibernateException {return original;}
}

然后在实体对象上加上@Type

@Type(type = "你的包名.DbEnumType")

修改Idea的Generate POJOs脚本,自动为枚举类型加上@Type,重新生成一遍实体类,跑unit test,颇费!(perfect)

是不是最佳实现我不知道,但完美满足我项目对枚举的要求,并代码足够精简就行了

解决JPA的枚举局限性相关推荐

  1. 如何解决win11“无法枚举容器中的对象,访问被拒绝”、“右键新建只有文件夹,没有其他选项”的问题。

    如何解决win11无法枚举容器中的对象.访问被拒绝.右键新建只有文件夹,没有其他选项的问题. 出错原因 举例示范 具体步骤 总结 形成原因:是文件夹Users权限的问题 解决方法:修改User权限即可 ...

  2. 解决JPA懒加载典型的N+1问题-注解@NamedEntityGraph

    解决JPA懒加载典型的N+1问题-注解@NamedEntityGraph - EalenXie - 博客园

  3. SpringBoot解决jpa,NoSession问题

    SpringBoot解决NoSession问题 package com.ihrm.system;import com.ihrm.common.utils.IdWorker; import com.ih ...

  4. 漏洞解决:用户名枚举

    漏洞描述 存在于系统登录页面,利用登陆时输入系统存在的"密码错误,5次后将锁定30分钟"和"用户名错误",返回不同的出错信息可枚举出系统中存在的账号信息. 解决 ...

  5. jpa多表联查动态_解决 JPA 多表动态查询

    前言 由于公司一直在使用 JPA 作为 ORM 框架,因此分配到需要多表复杂查询或动态查询都很头大.很多人对 JPA 抱有偏见,比如 JPA 只能处理简单的单表查询.下面总结下使用过的几种方法,前几种 ...

  6. 基于注解的Spring MVC与JPA如何解决实体的延时加载问题

    本文出处:http://blog.csdn.net/chaijunkun/article/details/7673931,转载请注明.由于本人不定期会整理相关博文,会对相应内容作出完善.因此强烈建议在 ...

  7. ABLIC推出“S-5701 B系列”TMR传感器IC,不但可以解决磁簧开关的局限性,而且具有耐用、小巧和寿命长的特点

    这种小型磁传感器IC仅需160nA的超低工作电流消耗,并具有较高的磁灵敏度 东京--(美国商业资讯)--艾普凌科有限公司(ABLIC Inc.)(总裁:石合信正,总部:东京都港区,下称"AB ...

  8. Spring Data JPA 常用注解

    1. 创建表 @Entity声明该类对应一个数据表实体(万事万物皆为对象). @Table 设置表名 @Entity @Table(name = "user") public cl ...

  9. 【CodeForces - 1041D】Glider (枚举起点,双指针 或 二分终点,思维)(知识点总结)

    题干: A plane is flying at a constant height of hh meters above the ground surface. Let's consider tha ...

最新文章

  1. Go 学习笔记(31)— 字符串 string、字符 rune、字节 byte、UTF-8 和 Unicode 区别以及获取字符串长度
  2. VS生成dll和lib库文件
  3. java的关键字和保留字_「Java」详解常见的53个关键字
  4. iOS MVC 介绍
  5. python3连接数据库步骤_Python3连接Mysql8.0遇到的问题及处理步骤
  6. maven如果正常配置不成功,就按照我的就可以配置成功了
  7. mysql监控优化(二)主从复制
  8. Linux基础知识总结一
  9. python怎么群发邮件_小工具:使用python群发邮件
  10. 无敌2_大师级鱼丸云吞终极海鲜面
  11. scp 保留文件属组_SCP命令用法详解-hdparm工具参数详解-改变文件组命令chgrp和改变文件属主命令chown_169IT.COM...
  12. 解决easyui combobox赋值boolean类型的值时,经常出现的内容显示的value而不是text的bug...
  13. 【微信小程序】微信小程序项目开发哔哩哔哩小程序
  14. 无需再怨恨“刘海屏”了,因为适配十分简单
  15. 小程序---365笔记第5天---常用方法
  16. 【一起学Rust】Rust学习前准备——注释和格式化输出
  17. 「伯克利大学」 的计算机入门教程
  18. oracle 强制还原一张表,oracle数据库中,用户不小心在生产环境中删除了一张比较重要的表,他想恢复该操作,你会采取什么样的...
  19. SA(需求分析师)笔试题目整理
  20. 机器人学基础(一)——机器人几何结构分类及其自由度

热门文章

  1. 国科大高级人工智能8-归结原理和horn子句
  2. C#多线程与并行编程方面的电子书,中英文版本
  3. BZOJ3064 CPU监控
  4. 个人做的一些小工具分享
  5. javascript如何处理很多数据,类似分页切换
  6. bootstrap table 的简单Demo
  7. [vagrant]vagrant centos静态ip设置
  8. 11月25号站立会议
  9. 12.4scrum report
  10. 在存储过程中使用系统存储过程sp_Excute的注意事项