2012-07-01 41 views
22

Estoy intentando persistir una entidad en PostgreSQL que usa UUID como clave principal. He intentado que persiste como un UUID claro:UUID persistente en PostgreSQL usando JPA

@Id 
@Column(name = "customer_id") 
private UUID id; 

Con lo anterior, me sale este error:

ERROR: column "customer_id" is of type uuid but expression is of type bytea 
Hint: You will need to rewrite or cast the expression. 
Position: 137 

También probé persistiendo el UUID como byte [] en vano:

@Transient 
private UUID id; 

@Id 
@Column(name = "customer_id") 
@Access(AccessType.PROPERTY) 
@Lob 
protected byte[] getRowId() { 
    return id.toString().getBytes(); 
} 

protected void setRowId(byte[] rowId) { 
    id = UUID.fromString(new String(rowId)); 
} 

Si elimino @Lob, el error que obtengo es el mismo que el publicado anteriormente. Pero con @Lob aplicada, el error cambia ligeramente a:

ERROR: column "customer_id" is of type uuid but expression is of type bigint 
Hint: You will need to rewrite or cast the expression. 
Position: 137 

me siento muy mal al no poder hacer algo tan simple como esto!

Estoy usando Hibernate 4.1.3.Final con PostgreSQL 9.1.

He visto numerosas preguntas sobre SO más o menos con el mismo problema, pero todas son viejas y ninguna parece tener una respuesta directa.

Me gustaría lograr esto de forma estándar sin recurrir a hacks feos. Pero si esto solo se puede lograr mediante (feos) hacks, entonces puede ser que sea eso lo que haré. Sin embargo, no quiero almacenar el UUID como varchar en la base de datos ya que no es bueno para el rendimiento. Además, me gustaría no introducir una dependencia de Hibernate en mi código si es posible.

Cualquier ayuda sería muy apreciada.

ACTUALIZA 1 (2012-07-03 24:15)

Bueno, bueno, bueno ... Es un poco interesante que he probado el mismo código (UUID llano, sin conversión - el primera versión del código publicado anteriormente) con SQL Server 2008 R2 usando el controlador JTDS (v1.2.5) y, adivina qué, funcionó como un encanto (por supuesto, tuve que cambiar la información relacionada con la conexión en persistence.xml).

Ahora, ¿es un problema específico de PostgreSQL o qué?

+1

posible duplicado de [Postgresql UUID compatible con Hibernate?] (Http://stackoverflow.com/questions/4495233/postgresql-uuid-supported-by -hibernate) –

+0

Como mencioné en mi publicación, hay muchas preguntas sobre SO con el mismo problema, pero todas son antiguas y ninguna parece tener una solución buena y funcional. La publicación que mencionaste no es una excepción (que, por cierto, vi antes de publicar mi pregunta). – ehsanullahjan

Respuesta

30

El controlador JDBC de PostgreSQL ha elegido una forma desafortunada de representar códigos de tipo no estándar JDBC.Simplemente asignan todos ellos a Types.OTHER. Para resumir, es necesario tener una correlación de tipo Hibernate especial para el manejo de las asignaciones UUID (a columnas de tipo de datos UUID postgres-específicas):

@Id 
@Column(name = "customer_id") 
@org.hibernate.annotations.Type(type="org.hibernate.type.PostgresUUIDType") 
private UUID id; 

o más sucintamente:

@Id 
@Column(name = "customer_id") 
@org.hibernate.annotations.Type(type="pg-uuid") 
private UUID id; 

Otro (mejor) es la opción para registrar org.hibernate.type.PostgresUUIDType como la asignación de tipo de Hibernate predeterminada para todos los atributos expuestos como java.utiliránID. Que se trata en la documentación @http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch06.html#types-registry

+1

+1 Esto funcionó: @ org.hibernate.annotations.Type (type = "pg-uuid") – ehsanullahjan

+0

Hola, probé esto pero obtuve una org.hibernate.MappingException: No Dialect mapping para el tipo JDBC: 1111. Cualquiera puede ¿ayuadame? – jonfornari

+0

@jonfornari Tuve el mismo problema, pero olvidé seleccionar org.hibernate.dialect.PostgreSQLDialect en mi proyecto de Grails. Tal vez te estás perdiendo esa o algunas otras configuraciones? No necesitaba lo siguiente, pero puede ser útil: http://andrew-arch.blogspot.com/2008/02/uuid-and-hibernate-second-update.html –

-4

Estoy seguro de que una vez que haya generado el UUID para un registro, será estático. Por lo tanto, no tiene sentido mapear UUID como un objeto a la base de datos y luego convertirlo de nuevo a Cadena en la recuperación.

Uso UUID como una cadena:

@Id 
@Column(name = "customer_id") 
private String id; 

//getter and setter 

public void setId(UUID id) { 
    this.id = id.toString(); 
} 

También hay algunas cosas a considerar:

  1. si desea utilizar ID, UUID o ambos. Hay quick discussion over this.
  2. Usando 'uuid' para nombres en lugar de 'id', es engañoso.
  3. Eliminando signos '-' del UUID, guarde 4 bytes.
  4. Generando UUID en la sección @PrePersist si usa Spring.
+0

Gracias por su respuesta, pero parece que no hizo mi pregunta por completo. Ya mencioné que almacenar el UUID como una cadena (varchar) en la base de datos iba a perjudicar el rendimiento, por lo que no era una opción a considerar. No es necesario jugar con el '-'s siempre que guarde el UUID como el tipo nativo del DB (frente a un varchar). En cuanto al enlace codinghorror, ya he pasado por eso. – ehsanullahjan

+0

Además, todo el motivo por el que hice las conversiones de uuid-string-uuid fue para solucionar el error en primer lugar. Pero, como se menciona en la pregunta, no funcionó. – ehsanullahjan

+0

@janlala todavía puede hacer ID como Long, ¿ahorrará rendimiento? – JMelnik

13

JPA 2.1 proporciona una manera muy fácil de usar el tipo de columna uuid PostgreSQL y java.util.UUID como el tipo de campo correspondiente entidad:

@javax.persistence.Converter(autoApply = true) 
public class PostgresUuidConverter implements AttributeConverter<UUID, UUID> { 

    @Override 
    public UUID convertToDatabaseColumn(UUID attribute) { 
     return attribute; 
    } 

    @Override 
    public UUID convertToEntityAttribute(UUID dbData) { 
     return dbData; 
    } 

} 

Sólo añadir esta clase a su configuración de persistencia y anotar campos UUID con @Column(columnDefinition="uuid").

+0

No teniendo suerte con esto. La anotación @Converter no se aplica a una clase. – ruckc

+0

No estoy seguro de qué quiere decir. Ambos javax.persistence.Converter y org.eclipse.persistence.annotations.Converter se pueden aplicar a una clase. Edité la respuesta para que sea explícito que usa javax.persistence.Converter. –

+0

Funcionó (PG9.4) - También la anotación @Column no es necesaria para mí. – Jack

1

La solución sugerida por Oleg no me funcionó a la perfección (falló si intentaba mantener un valor nulo). Aquí hay una solución ajustada que también funciona para valores nulos.

En mi caso, estoy usando EclipseLink, así que si está utilizando Hibernate es posible que no lo necesite.

public class UuidConverter implements AttributeConverter<UUID, Object> { 
    @Override 
    public Object convertToDatabaseColumn(UUID uuid) { 
     PGobject object = new PGobject(); 
     object.setType("uuid"); 
     try { 
      if (uuid == null) { 
       object.setValue(null); 
      } else { 
       object.setValue(uuid.toString()); 
      } 
     } catch (SQLException e) { 
      throw new IllegalArgumentException("Error when creating Postgres uuid", e); 
     } 
     return object; 
    } 

    @Override 
    public UUID convertToEntityAttribute(Object dbData) { 
     return (UUID) dbData; 
    } 
} 
2

reciente versión del controlador >= 8.4-701 PostgreSQL JDBC trata correctamente java.util.UUID mapeo. Y también Hibernate >= 4.3.x.

Ver detalles en https://stackoverflow.com/a/780922/173149:

Map the database uuid type to java.util.UUID. 
This only works for relatively new server (8.3) and JDK (1.5) versions. 
5

tenerlo trabajando con Hibernate 5.1.x, puede seguir Steve Ebersole comment here

@Id 
@GeneratedValue 
@Column(columnDefinition = "uuid", updatable = false) 
public UUID getId() { 
    return id; 
} 
+0

Haría dos veces la voz para esta respuesta si pudiera. La especificación columnDefinition me solucionó un problema cuando el UUID se reemplazó por el tipo de geometría durante la creación de las tablas. Sucedió porque se usaron PostgisDialect. –

0

Añade esta bandera en la conexión url PostgreSQL:? = StringType no especificado

así

jdbc: postgresql: // localhost: 5432/yourdatabase? Stringtype = no especificado

+0

Si bien esto puede responder a la pregunta, es mejor explicar las partes esenciales de la respuesta y posiblemente cuál fue el problema con el código OP. – pirho