Esenzioni di Java Enums, JPA e Postgres – Come faccio a farle funzionare insieme?

Abbiamo un DB postgres con enigmi postgres. Stiamo iniziando a build JPA nella nostra applicazione. Abbiamo anche enumerazioni Java che rispecchiano le enumerazioni postgres. Ora la grande domanda è come convincere JPA a capire le enumerazioni di Java da una parte e le enigmi di Postgres dall’altra? Il lato Java dovrebbe essere abbastanza facile, ma non sono sicuro di come fare il lato postgres.

Ciò comporta la creazione di più mapping.

Innanzitutto, l’enum Postgres viene restituito dal driver JDBC come un’istanza di tipo PGObject. La proprietà type di questo ha il nome dell’enumerazione postgres e la proprietà value il suo valore. (L’ordinale non è memorizzato comunque, quindi tecnicamente non è più un enum e forse completamente inutile a causa di questo)

Ad ogni modo, se hai una definizione come questa in Postgres:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); 

Quindi il set di risultati conterrà un PGObject con tipo “mood” e valore “happy” per una colonna che ha questo tipo enum e una riga con il valore ‘happy’.

La prossima cosa da fare è scrivere un codice interceptor che si trova tra il punto in cui JPA legge dal raw set di risultati e imposta il valore sulla tua quadro. Ad esempio, supponiamo di avere la seguente quadro in Java:

public @Entity class Person { public static enum Mood {sad, ok, happy} @Id Long ID; Mood mood; }
public @Entity class Person { public static enum Mood {sad, ok, happy} @Id Long ID; Mood mood; } 

Sfortunatamente, JPA non offre un facile punto di intercettazione in cui è ansible effettuare la conversione da PGObject a Java enum Mood. La maggior parte dei venditori JPA tuttavia ha un supporto proprietario per questo. Hibernate ad esempio ha le annotazioni TypeDef e Type per questo (da Hibernate-annotations.jar).

@TypeDef(name="myEnumConverter", typeClass=MyEnumConverter.class) public @Entity class Person { public static enum Mood {sad, ok, happy} @Id Long ID; @Type(type="myEnumConverter") Mood mood;
@TypeDef(name="myEnumConverter", typeClass=MyEnumConverter.class) public @Entity class Person { public static enum Mood {sad, ok, happy} @Id Long ID; @Type(type="myEnumConverter") Mood mood; 

Questi ti consentono di fornire un’istanza di UserType (da Hibernate-core.jar) che esegue la conversione effettiva:

public class MyEnumConverter implements UserType { private static final int[] SQL_TYPES = new int[]{Types.OTHER}; public Object nullSafeGet(ResultSet arg0, String[] arg1, Object arg2) throws HibernateException, SQLException { Object pgObject = arg0.getObject(X); // X is the column containing the enum try { Method valueMethod = pgObject.getClass().getMethod("getValue"); String value = (String)valueMethod.invoke(pgObject); return Mood.valueOf(value); } catch (Exception e) { e.printStackTrace(); } return null; } public int[] sqlTypes() { return SQL_TYPES; } // Rest of methods omitted }
public class MyEnumConverter implements UserType { private static final int[] SQL_TYPES = new int[]{Types.OTHER}; public Object nullSafeGet(ResultSet arg0, String[] arg1, Object arg2) throws HibernateException, SQLException { Object pgObject = arg0.getObject(X); // X is the column containing the enum try { Method valueMethod = pgObject.getClass().getMethod("getValue"); String value = (String)valueMethod.invoke(pgObject); return Mood.valueOf(value); } catch (Exception e) { e.printStackTrace(); } return null; } public int[] sqlTypes() { return SQL_TYPES; } // Rest of methods omitted } 

Questa non è una soluzione operativa completa, ma solo un puntatore veloce nella speranza, nella giusta direzione.

In realtà ho usato un modo più semplice rispetto a quello con PGObject e Converters. Dal momento che le enumerazioni di Postgres sono convertite in modo abbastanza naturale da testo, è sufficiente lasciare che faccia ciò che sa fare meglio. Prenderò in prestito l’esempio di stati d’animo di Arjan, se non gli dispiace:

Il tipo di enum in Postgres:

 CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); 

La class e enum in Java:

 public @Entity class Person { public static enum Mood {sad, ok, happy}; @Enumerated(EnumType.STRING) Mood mood; 

}

Quel tag @Enumerated dice che la serializzazione / deserializzazione dell’enum dovrebbe essere fatta nel testo. Senza di esso, usa int, che è più fastidioso di qualsiasi altra cosa.

A questo punto hai due opzioni. Neanche tu:

  1. Aggiungi stringtype = non specificato alla stringa di connessione, come spiegato nei parametri di connessione JDBC . Ciò consente a Postgres di indovinare il tipo di destra e convertire tutto in modo adeguato, dal momento che riceve qualcosa come “enum = unknown”, che è un’espressione che sa già cosa fare (alimenta il valore? Al deserializzatore di tipo sinistro). Questa è l’opzione preferita, poiché dovrebbe funzionare per tutti gli UDT semplici come le enumerazioni in un colpo solo.

O:

  1. Creare una conversione implicita da varchar all’enum nel database. Quindi in questo secondo caso il database riceve qualche incarico o confronto come ‘enum = varchar’ e trova una regola nel suo catalogo interno dicendo che può passare il valore della mano destra attraverso la funzione di serializzazione di varchar seguita dalla funzione di deserializzazione del enum. Questo è più di quanto dovrebbe essere necessario; e avere troppi cast impliciti nel catalogo può causare interrogazioni arbitrarie per avere interpretazioni ambigue, quindi usarlo con parsimonia. La creazione del cast è:

    CREATE CAST (IL CARATTERE È VARIAto come stato d’animo) CON SENZA COME IMPLICITO;

Dovrebbe funzionare proprio con quello.

Ho presentato una segnalazione di bug con una patch inclusa per Hibernate: HHH-5188

La patch funziona per me per leggere un enum PostgreSQL in un enum Java usando JPA.