如何使用 JPA 和 Hibernate 映射 PostgreSQL 枚举
Posted
技术标签:
【中文标题】如何使用 JPA 和 Hibernate 映射 PostgreSQL 枚举【英文标题】:How to map PostgreSQL enum with JPA and Hibernate 【发布时间】:2011-11-28 01:04:18 【问题描述】:我正在尝试将名为 transmission_result
的 PostgreSQL 自定义类型映射到 Hibernate/JPA POJO。 PostgreSQL 自定义类型或多或少是enum
类型的字符串值。
我创建了一个名为 PGEnumUserType
的自定义 EnumUserType
以及一个代表 PostgreSQL 枚举值的 enum
类。当我在真实数据库上运行它时,我收到以下错误:
'ERROR: column "status" is of type transmission_result but expression is of type
character varying
Hint: You will need to rewrite or cast the expression.
Position: 135 '
看到这个,我想我需要将我的SqlTypes
更改为Types.OTHER
。但是这样做会破坏我的集成测试(在内存数据库中使用 HyperSQL)并显示以下消息:
'Caused by: java.sql.SQLException: Table not found in statement
[select enrollment0_."id" as id1_47_0_,
enrollment0_."tpa_approval_id" as tpa2_47_0_,
enrollment0_."tpa_status_code" as tpa3_47_0_,
enrollment0_."status_message" as status4_47_0_,
enrollment0_."approval_id" as approval5_47_0_,
enrollment0_."transmission_date" as transmis6_47_0_,
enrollment0_."status" as status7_47_0_,
enrollment0_."transmitter" as transmit8_47_0_
from "transmissions" enrollment0_ where enrollment0_."id"=?]'
我不确定为什么更改 sqlType
会导致此错误。任何帮助表示赞赏。
JPA/休眠实体:
@Entity
@Access(javax.persistence.AccessType.PROPERTY)
@Table(name="transmissions")
public class EnrollmentCycleTransmission
// elements of enum status column
private static final String ACCEPTED_TRANSMISSION = "accepted";
private static final String REJECTED_TRANSMISSION = "rejected";
private static final String DUPLICATE_TRANSMISSION = "duplicate";
private static final String EXCEPTION_TRANSMISSION = "exception";
private static final String RETRY_TRANSMISSION = "retry";
private Long transmissionID;
private Long approvalID;
private Long transmitterID;
private TransmissionStatusType transmissionStatus;
private Date transmissionDate;
private String TPAApprovalID;
private String TPAStatusCode;
private String TPAStatusMessage;
@Column(name = "id")
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public Long getTransmissionID()
return transmissionID;
public void setTransmissionID(Long transmissionID)
this.transmissionID = transmissionID;
@Column(name = "approval_id")
public Long getApprovalID()
return approvalID;
public void setApprovalID(Long approvalID)
this.approvalID = approvalID;
@Column(name = "transmitter")
public Long getTransmitterID()
return transmitterID;
public void setTransmitterID(Long transmitterID)
this.transmitterID = transmitterID;
@Column(name = "status")
@Type(type = "org.fuwt.model.PGEnumUserType" , parameters =@org.hibernate.annotations.Parameter(name = "enumClassName",value = "org.fuwt.model.enrollment.TransmissionStatusType") )
public TransmissionStatusType getTransmissionStatus()
return this.transmissionStatus ;
public void setTransmissionStatus(TransmissionStatusType transmissionStatus)
this.transmissionStatus = transmissionStatus;
@Column(name = "transmission_date")
public Date getTransmissionDate()
return transmissionDate;
public void setTransmissionDate(Date transmissionDate)
this.transmissionDate = transmissionDate;
@Column(name = "tpa_approval_id")
public String getTPAApprovalID()
return TPAApprovalID;
public void setTPAApprovalID(String TPAApprovalID)
this.TPAApprovalID = TPAApprovalID;
@Column(name = "tpa_status_code")
public String getTPAStatusCode()
return TPAStatusCode;
public void setTPAStatusCode(String TPAStatusCode)
this.TPAStatusCode = TPAStatusCode;
@Column(name = "status_message")
public String getTPAStatusMessage()
return TPAStatusMessage;
public void setTPAStatusMessage(String TPAStatusMessage)
this.TPAStatusMessage = TPAStatusMessage;
自定义枚举用户类型:
public class PGEnumUserType implements UserType, ParameterizedType
private Class<Enum> enumClass;
public PGEnumUserType()
super();
public void setParameterValues(Properties parameters)
String enumClassName = parameters.getProperty("enumClassName");
try
enumClass = (Class<Enum>) Class.forName(enumClassName);
catch (ClassNotFoundException e)
throw new HibernateException("Enum class not found ", e);
public int[] sqlTypes()
return new int[] Types.VARCHAR;
public Class returnedClass()
return enumClass;
public boolean equals(Object x, Object y) throws HibernateException
return x==y;
public int hashCode(Object x) throws HibernateException
return x.hashCode();
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException
String name = rs.getString(names[0]);
return rs.wasNull() ? null: Enum.valueOf(enumClass,name);
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException
if (value == null)
st.setNull(index, Types.VARCHAR);
else
st.setString(index,((Enum) value).name());
public Object deepCopy(Object value) throws HibernateException
return value;
public boolean isMutable()
return false; //To change body of implemented methods use File | Settings | File Templates.
public Serializable disassemble(Object value) throws HibernateException
return (Enum) value;
public Object assemble(Serializable cached, Object owner) throws HibernateException
return cached;
public Object replace(Object original, Object target, Object owner) throws HibernateException
return original;
public Object fromXMLString(String xmlValue)
return Enum.valueOf(enumClass, xmlValue);
public String objectToSQLString(Object value)
return '\'' + ( (Enum) value ).name() + '\'';
public String toXMLString(Object value)
return ( (Enum) value ).name();
枚举类:
public enum TransmissionStatusType
accepted,
rejected,
duplicate,
exception,
retry
【问题讨论】:
也可能是由于没有从枚举转换为 varchar! 【参考方案1】:如果您在 PostgreSQL 中有以下 post_status_info
枚举类型:
CREATE TYPE post_status_info AS ENUM (
'PENDING',
'APPROVED',
'SPAM'
)
您可以使用以下自定义 Hibernate 类型轻松地将 Java Enum 映射到 PostgreSQL Enum 列类型:
public class PostgreSQLEnumType extends org.hibernate.type.EnumType
public void nullSafeSet(
PreparedStatement st,
Object value,
int index,
SharedSessionContractImplementor session)
throws HibernateException, SQLException
if(value == null)
st.setNull( index, Types.OTHER );
else
st.setObject(
index,
value.toString(),
Types.OTHER
);
要使用它,您需要使用 Hibernate @Type
注释对字段进行注释,如下例所示:
@Entity(name = "Post")
@Table(name = "post")
@TypeDef(
name = "pgsql_enum",
typeClass = PostgreSQLEnumType.class
)
public static class Post
@Id
private Long id;
private String title;
@Enumerated(EnumType.STRING)
@Column(columnDefinition = "post_status_info")
@Type( type = "pgsql_enum" )
private PostStatus status;
//Getters and setters omitted for brevity
就是这样,它就像一个魅力。这是test on GitHub that proves it。
【讨论】:
哇!非常感谢您的贡献,这节省了我的时间! (GitHub 示例很完美) 很高兴能帮上忙。【参考方案2】:以下内容也可能有助于让 Postgres 以静默方式将字符串转换为您的 SQL 枚举类型,这样您就可以使用 @Enumerated(STRING)
而不需要 @Type
。
CREATE CAST (character varying as post_status_type) WITH INOUT AS IMPLICIT;
【讨论】:
【参考方案3】:build.gradle.kts
dependencies
api("javax.persistence", "javax.persistence-api", "2.2")
api("org.hibernate", "hibernate-core", "5.4.21.Final")
在 Kotlin 中,使用 EnumType<Enum<*>>()
进行通用扩展很重要
PostgreSQLEnumType.kt
import org.hibernate.type.EnumType
import java.sql.Types
class PostgreSQLEnumType : EnumType<Enum<*>>()
@Throws(HibernateException::class, SQLException::class)
override fun nullSafeSet(
st: PreparedStatement,
value: Any,
index: Int,
session: SharedSessionContractImplementor)
st.setObject(
index,
value.toString(),
Types.OTHER
)
Custom.kt
import org.hibernate.annotations.Type
import org.hibernate.annotations.TypeDef
import javax.persistence.*
@Entity
@Table(name = "custom")
@TypeDef(name = "pgsql_enum", typeClass = PostgreSQLEnumType::class)
data class Custom(
@Id @GeneratedValue @Column(name = "id")
val id: Int,
@Enumerated(EnumType.STRING) @Column(name = "status_custom") @Type(type = "pgsql_enum")
val statusCustom: StatusCustom
)
enum class StatusCustom
FIRST, SECOND
我不推荐的一个更简单的选项是Arthur's answer 中的第一个选项,它在 db 的连接 URL 中添加一个参数,这样枚举数据类型就不会丢失。我相信后端服务器和数据库之间的数据类型映射的责任恰恰是后端。
<property name="connection.url">jdbc:postgresql://localhost:5432/yourdatabase?stringtype=unspecified</property>
Source
【讨论】:
【参考方案4】:一个快速的解决方案将是
jdbc:postgresql://localhost:5432/postgres?stringtype=unspecified
?stringtype=unspecified就是答案
【讨论】:
【参考方案5】:我想通了。我需要在 nullSafeSet 函数中使用 setObject 而不是 setString,并将 Types.OTHER 作为 java.sql.type 传递,让 jdbc 知道它是 postgres 类型。
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException
if (value == null)
st.setNull(index, Types.VARCHAR);
else
// previously used setString, but this causes postgresql to bark about incompatible types.
// now using setObject passing in the java type for the postgres enum object
// st.setString(index,((Enum) value).name());
st.setObject(index,((Enum) value), Types.OTHER);
【讨论】:
以上是关于如何使用 JPA 和 Hibernate 映射 PostgreSQL 枚举的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 JPA 和 Hibernate 将 MySQL JSON 列映射到 Java 实体属性
如何在pojo中用jpa或hibernate映射json字段?
如何使用 Spring Data JPA(Hibernate) 跨映射表过滤关联实体?
如何使用 JPA/Hibernate 注释将 MySQL char(n) 列映射到实例变量?