jpa 将UUID与EclipseLink和PostgreSQL配合使用

xurqigkl  于 2022-11-14  发布在  Eclipse
关注(0)|答案(6)|浏览(152)

我想使用PostgreSQL uuid类型作为对象的主键。为此我创建了一个转换器(实现了Converter接口)。下面是相关代码:

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

然后,我用下面的注解对相关实体X进行了注解:

@Converter(name="uuidConverter",converterCalss=UUIDConverter.class)
@Convert("uuidConverter")
@Id
public UUID getId()
{
    return id;
}

问题是我有另一个类(Y),它有以下定义:

@ManyToOne(targetEntity = X.class)
@JoinColumn(name = "x_id")
public X getX();

尽管EclipseLink按预期创建了表,但在尝试插入Y类型的对象时,它会向数据库发送一个字符串。Postgres返回以下错误消息:

column "id" is of type uuid but expression is of type character varying at character

任何解决方案/变通方案都将受到赞赏。

sgtfey8w

sgtfey8w1#

我在使用EclipseLink JPA + Postgresql + UUID作为主键时遇到了同样的问题。
为了解决这个问题,我合并了Github和下面链接的代码:https://forums.oracle.com/forums/thread.jspa?messageID=4584157
下面的UUIDConverter代码对我很有效,尽管这段代码肯定不是最好的。

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

de90aj5v2#

我在使用EclipseLink JPA 2.1 + Postgresql + UUID作为主键时遇到了一些问题,但我找到了不同的解决方案。我采用了AttributeConverter,但我在EclipseLink实现中遇到了一个问题,我用以下代码解决了这个问题:

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

}

正如我在这篇文章中详细解释的那样http://blog-ungarida.rhcloud.com/persisting-uuid-in-postgresql-using-jpa-eclipselink/

tmb3ates

tmb3ates3#

尝试检查initialize方法中Map的fieldClassification是什么。它可能以某种方式获取了String.class,尝试将其设置为Object.class。
或字段.setType(对象.类别)

72qzrwbm

72qzrwbm4#

EclipseLink和PostgresQL之间似乎存在bug/不兼容。如果你只是使用UUID作为主键,你应该没问题。但是如果你有一个可以为空的UUID列,并且你试图在其中存储空值,你会得到报告的错误:

column "whatever" is of type uuid but expression is of type character varying

请看:https://bugs.eclipse.org/bugs/show_bug.cgi?id=538138(如果你有时间的话,请登录并为它投票!)
这个错误报告对我非常有用。特别是论坛线程的链接:
https://www.eclipse.org/forums/index.php?t=msg&th=1073632&goto=1719530&#msg_1719530
我在SO上尝试了各种各样的解决方案,在Web上的其他地方也尝试了各种各样的解决方案。唯一一个似乎对我有效的是大卫惠勒在那里发布的解决方案。具体来说,就是在数据库中创建一个从character varyinguuid的转换。
请注意,您必须是postgres用户才能创建转换:

$ 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;

为了完整起见,这里是我使用的其余部分(如果有帮助的话)
我的所有实体(具有UUID主键)都扩展了一个名为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;
   }
}

UUID转换器类如下所示:

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

如果实体得UUID列不是主键,则可以按如下方式对其进行注解:

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;

}

请注意,如果该实体有其他使用标准javax.persistence.convert注解的列,则需要区分这两个Convert注解以避免编译错误。
例如:

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

希望这能为别人节省一些时间,祝你好运!

xfb7svmp

xfb7svmp5#

适用于EclipseLink的通用UUID转换器(不仅适用于PostgreSQL)

代码:

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

    }
}
ftf50wuq

ftf50wuq6#

您不需要转换的。请在实体中使用此列定义。您需要首先注册uuid扩展名。这适用于Postgres 10和Wildfly 10.1

@Column(name = "UUID", nullable=false, insertable = false, columnDefinition="uuid DEFAULT uuid_generate_v4()")
private String uuid;

相关问题