解决JPA的枚举局限性

对于数据字典型字段,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)

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/496445.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络模拟器 eNSP、EVE-NG、GNS3、Packet Tracert

工欲善其事必先利其器&#xff0c;学习网络不可能都有真实的网络环境可以使用&#xff0c;这时就需要使用网络模拟软件&#xff0c;模拟一些网络环境&#xff0c;构建一些网络拓扑&#xff0c;然后学习研究网络技术 初学时不要在模拟器的选择上纠结&#xff0c;PT、GNS3 就足够…

linux 升级java_linux 升级jdk1.8

1、首先根据实际情况准备好包 jdk-8u45-linux-x64.rpm2、先不管是否已安装JDK1.6还是JDK1.7&#xff0c;先下载 jdk-8u45-linux-x64.rpm 然后上传到 /usr/local/src 去。当然其他目录也可以。这里是默认位置。3、给所有用户添加可执行权限#chmod x jdk-8u45-linux-x64.rpm都给…

第四讲 构建安全的Microsoft ASP.NET 应用的最佳实践和技术

*ASP.NET应用安全事项 *输入验证 1、参数化查询 2、存储过程 3、html编码(防止javascript脚本执行) String.Format("Invalid Logon for {0},please try again!",Server.HtmlEncode(UserName)); 认证与授权 配置管理 Review production configuration: <customErro…

信息技术守护人类文明DNA

来源&#xff1a;中国青年报 作者&#xff1a;胡春艳21世纪最时髦的技术&#xff0c;改变的不仅是未来&#xff0c;在被称为“最古老的研究”的文物与博物馆界&#xff0c;感受到的变化可能更加显著。天津大学文化遗产保护与传承信息技术研究中心主任张加万团队历时10年&…

如何给正面的负反馈

麦肯锡的一个方法论&#xff0c;就是如何给正面的负反馈。 如果你想给别人提意见的时候&#xff0c;用什么样的方法最有建设性&#xff1f; &#xff08;1&#xff09;我观察到你说了什么话&#xff0c;做了什么事&#xff1a;一、二、三、四…… &#xff08;2&#xff0…

java 单例方法_Java-单例模式 - 惊觉

单例模式单例(Singleton)模式是设计模式之一&#xff0c;最显著的特点就是一个类在一个JVM中只有一个实例&#xff0c;避免繁琐的创建销毁实例。简单例子先看简单的单例模式实现完整代码&#xff1a;Singleton_Test类使用单例模式 &#xff0c;采用饿汉式方法。public class Si…

DIY协同办公平台(C/S)系列4之通讯录篇

通讯录分为企业通讯录和员工个人通讯录。企业通讯录与企业员工资料相连接&#xff0c;读取其资料。而员工个人通讯录主要是员工的自己业务往来和个人的通讯资料保存。 1.主体toolbar 用于快捷掉出与员工相关的功能&#xff0c;打算支持自定义和本地化&#xff0c;通过对文本的…

iRank: 基于互联网类脑架构的阿尔法鹰眼发展趋势评估

前言&#xff1a;iRank是人工智能学家基于科学院研究团队“互联网类脑智能巨系统架构”和”AI 智商评估模型”等研究成果&#xff0c;对智能产业优秀企业、产品和技术进行评估、发掘和推荐的项目。iRank目前拥有互联网、人工智能、机器人&#xff0c;大数据、通讯技术、芯片、脑…

Java加密与解密的艺术~AES-GCM-NoPadding实现

来源&#xff1a;Java AES加密和解密_一名可爱的技术搬运工-CSDN博客 高级加密标准 &#xff08;AES&#xff0c;Rijndael&#xff09;是一种分组密码加密和解密算法&#xff0c;是全球使用最广泛的加密算法。 AES使用128、192或256位的密钥来处理128位的块。 本文向您展示了一…

STL bitset用法总结

c bitset类用法&#xff1a;http://blog.csdn.net/qll125596718/article/details/6901935 C 参考&#xff1a;http://www.cplusplus.com/reference/bitset/bitset/ http://happyboy200032.blog.163.com/blog/static/46903113201291252033712/ http://blog.csdn.net/e68948…

ASP.NET数据库编程入门

因为Access并不真正为高性能应用程序服务&#xff0c;所以一个希望有多个同时连接用户的站点必须部署一个比Access更适合的数据源。本文将讲述如何从Microsoft SQL Server(一种企业级RDMS)中获取数据。 SQL Server完全版包括了三个部分。第一个是引擎&#xff0c;用于实际组织数…

全球智能制造发展现状及前景预测 工业机器人引领行业发展

来源&#xff1a;前瞻产业研究院全球智能制造发展历程智能制造是伴随信息技术的不断普及而逐步发展起来的。1988年&#xff0c;美国纽约大学的怀特教授&#xff08;P&#xff0e;K&#xff0e;Wright&#xff09;和卡内基梅隆大学的布恩教授&#xff08;D&#xff0e;A&#xf…

vb整合多个excel表格到一张_VB合并工作表下载

VB合并工作表最新版是一款功能强大且界面简洁美观的excel表格合并工具&#xff0c;VB合并工作表最新版操作简便且易上手可以运行稳定&#xff0c;能够为大家解决合成打开卡顿等问题&#xff0c;实现多薄多表合并&#xff0c;VB合并工作表官方版还可以有效轻松提升工作效率。软件…

sql server 模糊查询

模糊查询——like select * from 表名where id like %2%select * from 表名where id like d%% escape d 查询时&#xff0c;字段中的内容并不一定与查询内容完全匹配&#xff0c;只要字段中含有这些内容 模糊查询&#xff0d;&#xff0d;is null select * from 表名 where na…

java.security.InvalidKeyException: Illegal key size

来源&#xff1a;https://blog.csdn.net/dling8/article/details/84061948 今天在使用idea打包maven项目时&#xff0c;出现这个错误&#xff1a;java.security.InvalidKeyException: Illegal key size。&#xff08;测试也可能会出现这个问题&#xff09;貌似是因为加密出现了…

NAT 详解

NAT技术(一、二、三、四、五) 系列&#xff1a;https://blog.51cto.com/wwwcisco/category1.html CCNA学习笔记之NAT&#xff1a;http://sweetpotato.blog.51cto.com/533893/1392884 网络地址转换NAT原理及应用&#xff1a;http://blog.csdn.net/xiaofei0859/article/details/…

趋势|人工智能疯狂洗脑,最聪明的钱已转向这16项技术

来源&#xff1a;21世纪商业评论 摘要&#xff1a;Gartner今年发布的技术成熟曲线中&#xff0c;出现了16个处于上升阶段的新兴技术&#xff0c;这些正在崛起的技术&#xff0c;或将成为企业未来几年战略性技术趋势的热点。不久前&#xff0c;信息技术研究公司Gartner发布了201…

action在java_@Action(value=/login在java语句中是什么意思

展开全部struts中采用注解配置Action需要导入struts2-convention-plugin的jar包Action省略getters和setters32313133353236313431303231363533e4b893e5b19e31333337386633ParentPackage("xystruts-default")Namespace("/login")public class LoginAction e…

JS省市二级联动

<SCRIPT LANGUAGE"JavaScript"> <!-- ###中国省市二级联动菜单开始## function Dsy() { this.Items {}; } Dsy.prototype.add function(id,iArray) { this.Items[id] iArray; } Dsy.prototype.Exists function(id) { if(typeof(this.Items[i…

OSI七层协议模型和TCP/IP四层模型

TCP/IP 协议栈及 OSI 参考模型详解&#xff1a;https://blog.csdn.net/guobing19871024/article/details/79415846 OSI七层网络模型&#xff0c;TCP/IP四层网络模型与网络协议解析&#xff1a;http://www.360doc.com/content/13/1123/17/7267612_331579105.shtml [网络必学]TCP…