【问题标题】:How to map PostgreSQL enum with JPA and Hibernate如何使用 JPA 和 Hibernate 映射 PostgreSQL 枚举
【发布时间】:2023-03-20 21:32:01
【问题描述】:

我正在尝试将名为 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!

标签: java postgresql hibernate jpa enums


【解决方案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&lt;Enum&lt;*&gt;&gt;() 进行通用扩展很重要

    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);
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2018-04-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-30
          • 2022-12-14
          • 1970-01-01
          • 2014-11-24
          • 2014-02-21
          相关资源
          最近更新 更多