19 Stimmen

Wie man PostgreSQL-Enum mit JPA und Hibernate mappt

Ich versuche, einen PostgreSQL-Custom-Typ, namens transmission_result, auf ein Hibernate/JPA POJO abzubilden. Der PostgreSQL-Custom-Typ ist mehr oder weniger ein enum-Typ von Zeichenfolgenwerten.

Ich habe einen benutzerdefinierten EnumUserType namens PGEnumUserType erstellt, sowie eine enum-Klasse, die die PostgreSQL aufgezählten Werte darstellt. Wenn ich dies gegen eine echte Datenbank ausführe, erhalte ich den folgenden Fehler:

'FEHLER: Die Spalte "status" ist vom Typ transmission_result, aber der Ausdruck ist vom Typ Zeichenfolge 
  Hinweis: Sie müssen den Ausdruck neu schreiben oder umwandeln.
  Position: 135 '

Als ich das sah, dachte ich, ich müsste meine SqlTypes in Types.OTHER ändern. Aber dabei brechen meine Integrationstests (mit einer HyperSQL im Arbeitsspeicher-DB) mit der Meldung ab:

'Verursacht durch: java.sql.SQLException: Tabelle nicht gefunden im Statement
[Auswahl von enrollment0_."id" als id1_47_0_,
 enrollment0_."tpa_approval_id" als tpa2_47_0_,
 enrollment0_."tpa_status_code" als tpa3_47_0_,
 enrollment0_."status_message" als status4_47_0_,
 enrollment0_."approval_id" as approval5_47_0_,
 enrollment0_."transmission_date" as transmis6_47_0_,
 enrollment0_."status" als status7_47_0_,
 enrollment0_."transmitter" als transmit8_47_0_
 von "transmissions" enrollment0_ where enrollment0_."id"=?]'

Ich bin mir nicht sicher, warum die Änderung des sqlType zu diesem Fehler führt. Jede Hilfe wird geschätzt.

JPA/Hibernate-Entität:

@Entity
@Access(javax.persistence.AccessType.PROPERTY)
@Table(name="transmissions")
public class EnrollmentCycleTransmission {

// Elemente der Statusspalte des Enums
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;
}
}

Benutzerdefinierter EnumUserType:

public class PGEnumUserType implements UserType, ParameterizedType {

private Class enumClass;

public PGEnumUserType(){
    super();
}

public void setParameterValues(Properties parameters) {
    String enumClassName = parameters.getProperty("enumClassName");
    try {
        enumClass = (Class) 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();
}
}

Enum-Klasse:

public enum TransmissionStatusType {
accepted,
rejected,
duplicate,
exception,
retry}

23voto

Vlad Mihalcea Punkte 121171

Wenn Sie den folgenden post_status_info Enum-Typ in PostgreSQL haben:

CREATE TYPE post_status_info AS ENUM (
    'PENDING', 
    'APPROVED', 
    'SPAM'
)

Sie können ganz einfach einen Java-Enum auf einen PostgreSQL-Enum-Spalten-Typ mappen, indem Sie den folgenden benutzerdefinierten Hibernate-Typ verwenden:

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

Um es zu verwenden, müssen Sie das Feld mit der Hibernate @Type Annotation versehen, wie im folgenden Beispiel illustriert:

@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
}

Das ist es, es funktioniert wie ein Zauber. Hier ist ein Test auf GitHub, der es beweist.

15voto

vikash dat Punkte 1444

Ich habe es herausgefunden. Ich musste setObject anstelle von setString in der nullSafeSet-Funktion verwenden und Types.OTHER als den java.sql.type übergeben, damit jdbc weiß, dass es sich um einen Postgres-Typ handelte.

public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
    if (value == null) {
        st.setNull(index, Types.VARCHAR);
    }
    else {
//            zuvor setString verwendet, aber das verursacht Probleme mit inkompatiblen Typen in Postgres.
//            jetzt setObject verwenden und den javatyp für das Postgres-Enum-Objekt übergeben
//            st.setString(index, ((Enum) value).name());
        st.setObject(index, ((Enum) value), Types.OTHER);
    }
}

7voto

lathspell Punkte 2818

Das Folgende könnte auch helfen, damit Postgres Strings stillschweigend in Ihren SQL Enum-Typ umwandelt, sodass Sie @Enumerated(STRING) verwenden können und kein @Type benötigen.

CREATE CAST (character varying as post_status_type) WITH INOUT AS IMPLICIT;

3voto

abbas Punkte 5423

Eine schnelle Lösung wird sein

jdbc:postgresql://localhost:5432/postgres?stringtype=unspecified

?Stringtyp=unspecified ist die Antwort

3voto

Braian Coronel Punkte 19992

build.gradle.kts

dependencies {
    api("javax.persistence", "javax.persistence-api", "2.2")
    api("org.hibernate",  "hibernate-core",  "5.4.21.Final")
}

In Kotlin ist es wichtig, eine generische Erweiterung mit EnumType>() zu erstellen

PostgreSQLEnumType.kt

import org.hibernate.type.EnumType
import java.sql.Types

class PostgreSQLEnumType : EnumType>() {

    @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
}

Ein einfacherer, aber nicht empfehlenswerterer Ansatz ist die erste Option in Arthur's Antwort, bei der ein Parameter in der Verbindungs-URL zur Datenbank hinzugefügt wird, damit der Enum-Datentyp nicht verloren geht. Ich bin der Meinung, dass die Verantwortung für das Mapping des Datentyps zwischen dem Backend-Server und der Datenbank genau beim Backend liegt.

jdbc:postgresql://localhost:5432/yourdatabase?stringtype=unspecified

Quelle


CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X