Reputation: 6741
I want to use the PostgreSQL uuid type for objects' primary keys. For that I've created a converter (implementing the Converter interface). Bellow is the relevant code:
@Override
public void initialize(DatabaseMapping mapping, Session session) {
final DatabaseField field;
if (mapping instanceof DirectCollectionMapping) {
field = ((DirectCollectionMapping) mapping).getDirectField();
} else {
field = mapping.getField();
}
field.setSqlType(Types.OTHER);
field.setTypeName("uuid");
field.setColumnDefinition("UUID");
}
Then I've annotated the relevant entity X with the bellow annotations:
@Converter(name="uuidConverter",converterCalss=UUIDConverter.class)
@Convert("uuidConverter")
@Id
public UUID getId()
{
return id;
}
The problem is that I have another class (Y) which has the following definition:
@ManyToOne(targetEntity = X.class)
@JoinColumn(name = "x_id")
public X getX();
Although EclipseLink created the tables as expected it sends a string to the database when trying to insert objects of type Y. Postgres returns the following error message:
column "id" is of type uuid but expression is of type character varying at character
Any solutions / work around will be appreciated.
Upvotes: 4
Views: 6460
Reputation: 101810
It seems there is a bug/incompatibility between EclipseLink and PostgresQL. If you just use UUID for primary keys you should be okay. But if you have a nullable UUID column, and you try to store null in it, you will get the reported error:
column "whatever" is of type uuid but expression is of type character varying
See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=538138 (log in and vote for it if you have the time!)
That bug report proved very useful to me. Specifically the link to the forum thread at:
https://www.eclipse.org/forums/index.php?t=msg&th=1073632&goto=1719530&#msg_1719530
I tried all sorts of solutions from here on SO, and elsewhere on the web. The only one that seemed to work for me was the one posted by David Wheeler there. Specifically, creating a cast from character varying
to uuid
in the database.
Note that you have to be user postgres to create the cast:
$ sudo su - postgres
$ psql <your database name>
# drop cast if exists (character varying as uuid);
# create or replace function uuid(_text character varying) returns uuid language sql as 'select uuid_in(_text::cstring)';
# create cast (character varying as uuid) with function uuid(character varying) as assignment;
For completeness here is the rest of what I use (in case it helps)
All my entities (that have a UUID primary key) extend a base class called EntityBase
:
package com.example.entity;
import java.io.Serializable;
import java.util.UUID;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.Converter;
import com.example.converter.UUIDTypeConverter;
@MappedSuperclass
@Converter(name="uuidConverter", converterClass=UUIDTypeConverter.class)
public class EntityBase implements Serializable, Cloneable
{
private static final long serialVersionUID = 1L;
@Id
@Convert("uuidConverter")
private UUID id;
public EntityBase() {
this.id = UUID.randomUUID();
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof EntityBase)) {
return false;
}
EntityBase other = (EntityBase) obj;
return getId().equals(other.getId());
}
public UUID getId()
{
return this.id;
}
public void setId(UUID id)
{
this.id = id;
}
}
The UUID converter class looks like this:
package com.example.converter;
import java.sql.Types;
import java.util.UUID;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.sessions.Session;
public class UUIDTypeConverter implements Converter
{
@Override
public UUID convertObjectValueToDataValue(Object objectValue, Session session)
{
return (UUID) objectValue;
}
@Override
public UUID convertDataValueToObjectValue(Object dataValue, Session session)
{
return (UUID) dataValue;
}
@Override
public boolean isMutable()
{
return true;
}
@Override
public void initialize(DatabaseMapping mapping, Session session)
{
DatabaseField field = mapping.getField();
field.setSqlType(Types.OTHER);
field.setTypeName("java.util.UUID");
field.setColumnDefinition("UUID");
}
}
If you have entities that have UUID columns that are not primary keys, you can annotate them as follows:
import org.eclipse.persistence.annotations.Convert
import org.eclipse.persistence.annotations.Converter;
@Entity
@Converter(name="uuidConverter", converterClass=UUIDTypeConverter.class)
public class BillingEvent extends EntityBase
{
@Convert("uuidConverter")
private UUID entityId;
}
Note that if that entity has other columns that use the standard javax.persistence.convert
annotation, you'll need to differentiate the two Convert
annotations to avoid a compile error.
For example:
import javax.persistence.Convert;
import org.eclipse.persistence.annotations.Converter;
@Entity
@Converter(name="uuidConverter", converterClass=UUIDTypeConverter.class)
public class BillingEvent extends EntityBase
{
@org.eclipse.persistence.annotations.Convert("uuidConverter")
private UUID entityId;
@Convert(converter = JSR310InstantTypeConverter.class)
private Instant createdOn;
}
I hope this saves others some time. Good luck!
Upvotes: 1
Reputation: 277
You don't need a converted. Use this column definition in the entity. You need to register the uuid extension first. This works with Postgres 10 and Wildfly 10.1
@Column(name = "UUID", nullable=false, insertable = false, columnDefinition="uuid DEFAULT uuid_generate_v4()")
private String uuid;
Upvotes: 0
Reputation: 1
Universal UUIDConverter for EclipseLink (not only PostgreSQL)
Code:
import java.nio.ByteBuffer;
import java.util.UUID;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DirectCollectionMapping;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.sessions.Session;
public class UUIDConverter implements Converter {
private Boolean isUUIDasByteArray = true;
@Override
public Object convertObjectValueToDataValue(Object objectValue,
Session session) {
if (isUUIDasByteArray) {
UUID uuid = (UUID)objectValue;
if (uuid == null) return null;
byte[] buffer = new byte[16];
ByteBuffer bb = ByteBuffer.wrap(buffer);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
return buffer;
}
return objectValue;
}
@Override
public UUID convertDataValueToObjectValue(Object dataValue,
Session session) {
if (isUUIDasByteArray) {
byte[] bytes = (byte[])dataValue;
if (bytes == null) return null;
ByteBuffer bb = ByteBuffer.wrap(bytes);
long high = bb.getLong();
long low = bb.getLong();
return new UUID(high, low);
}
return (UUID) dataValue;
}
@Override
public boolean isMutable() {
return true;
}
@Override
public void initialize(DatabaseMapping mapping, Session session) {
final DatabaseField field;
if (mapping instanceof DirectCollectionMapping) {
// handle @ElementCollection...
field = ((DirectCollectionMapping) mapping).getDirectField();
} else {
field = mapping.getField();
}
if (session != null && session.getLogin()!= null && session.getLogin().getPlatform() != null) {
String platform = session.getLogin().getPlatform().getClass().getSimpleName();
if (platform.equals("PostgreSQLPlatform")) {
field.setSqlType(java.sql.Types.OTHER);
field.setTypeName("java.util.UUID");
field.setColumnDefinition("UUID");
isUUIDasByteArray = false;
} else if (platform.equals("H2Platform")) {
field.setColumnDefinition("UUID");
} else if (platform.equals("OraclePlatform")) {
field.setColumnDefinition("RAW(16)");
} else if (platform.equals("MySQLPlatform")) {
field.setColumnDefinition("BINARY(16)");
} else if (platform.equals("SQLServerPlatform")) {
field.setColumnDefinition("UNIQUEIDENTIFIER");
}
}
}
}
Upvotes: 0
Reputation: 1960
I had some issues with EclipseLink JPA 2.1 + Postgresql + UUID as primary key but I find out different solution. I adopted AttributeConverter but I faced a problem with EclipseLink implementation that I resolved with this code:
@javax.persistence.Converter(autoApply = true)
public class PostgresUuidConverter implements AttributeConverter<UUID, Object> {
@Override
public Object convertToDatabaseColumn(UUID uuid) {
PostgresUuid object = new PostgresUuid();
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) {
if (dbData instanceof String) {
return UUID.fromString(dbData.toString());
} else {
return (UUID) dbData;
}
}
}
public class PostgresUuid extends PGobject implements Comparable<Object> {
private static final long serialVersionUID = 1L;
@Override
public int compareTo(Object arg0) {
return 0;
}
}
As I exaplined in detail in this post http://blog-ungarida.rhcloud.com/persisting-uuid-in-postgresql-using-jpa-eclipselink/
Upvotes: 2
Reputation: 44
I had the same issue with EclipseLink JPA + Postgresql + UUID as primary key.
To solve it, I've merged codes from Github and below link: https://forums.oracle.com/forums/thread.jspa?messageID=4584157
The below code for UUIDConverter worked for me, though the code surely isn't the best.
public void initialize(DatabaseMapping ARGMapping, Session ARGSession)
{
final DatabaseField Field;
if (ARGMapping instanceof DirectCollectionMapping)
{
Field = ((DirectCollectionMapping) ARGMapping).getDirectField();
}
else
{
Field = ARGMapping.getField();
}
Field.setSqlType(Types.OTHER);
Field.setTypeName("uuid");
Field.setColumnDefinition("UUID");
for (DatabaseMapping m : ARGMapping.getDescriptor().getMappings())
{
assert OneToOneMapping.class.isAssignableFrom(ManyToOneMapping.class);
if (m instanceof OneToOneMapping)
{
for (DatabaseField field : ((OneToOneMapping) m).getForeignKeyFields())
{
field.setSqlType(Types.OTHER);
field.setColumnDefinition("UUID");
field.setTypeName("uuid");
}
}
}
}
Upvotes: 2
Reputation: 18379
Try checking what the fieldClassification of the mapping is in the initialize method. It might be getting String.class somehow, try setting it to Object.class.
or, field.setType(Object.class)
Upvotes: 1