Hibernate是否支持Postgresql的UUID?

23

我无法让Hibernate与PostgreSQL一起使用java.util.UUID。

这是使用javax.persistence.*注释的映射:

private UUID itemUuid;

@Column(name="item_uuid",columnDefinition="uuid NOT NULL")
public UUID getItemUuid() {
    return itemUuid;
}

public void setItemUuid(UUID itemUuid) {
    this.itemUuid = itemUuid;
}
当持久化一个瞬态对象时,我会得到一个 SQLGrammarException 异常:
column "item_uuid" is of type uuid but expression is of type bytea at character 149

PostgreSQL版本为8.4.4
JDBC驱动程序- 8.4.4-702(也尝试了9.0-相同的情况)
Hibernate版本为3.6,主要配置属性:

<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
<property name="hibernate.connection.url">jdbc:postgresql://192.168.1.1/db_test</property>
8个回答

43

可以通过将以下注释添加到UUID中来解决此问题:

import org.hibernate.annotations.Type;
...
@Type(type="pg-uuid")
private java.util.UUID itemUuid;

至于为什么Hibernate不把这个设置作为默认设置,我无法告诉你......

更新: 使用createNativeQuery方法打开具有UUID字段的对象仍然存在问题。幸运的是,对我来说,createQuery方法迄今为止运行良好。


1
如果您没有告诉Hibernate期望返回的类型,那么createNativeQuery的问题就会出现。问题在于Postgres决定将多个自定义数据类型映射到JDBC驱动程序中的JDBC类型代码OTHER。因此,在没有用户指示的情况下从结果集中读取这些值时,Hibernate必须决定使用哪个。它基于在Dialect中注册的类型映射来进行决策。 - Steve Ebersole
4
如果你想在不指定查询类型信息的情况下让它工作,那么创建一个定制的方言(扩展现在使用的Postgres方言),并在其构造函数中调用以下命令:registerHibernateType(Types.OTHER,"pg-uuid"); - Steve Ebersole
3
无法连接到 H2 数据库。Caused by: org.hibernate.MappingException: No Dialect mapping for JDBC type: 1111 - daydreamer
11
在最新版本的Hibernate(5.2.12)中,@Type(type="pg-uuid")未被识别。我使用@Type(type="org.hibernate.type.PostgresUUIDType")来解决这个问题。 - Sri

10
现在您也可以使用java.util.UUID提供的UUID类,该类可以通过Hibernate映射到Postgres的uuid数据类型,而不需要在从数据库读取/写入时进行任何转换。
  @Id
  @GeneratedValue
  private UUID id;

默认情况下,生成的值是自动的,这使得您的JVM定义UUID。这也允许Hibernate使用批量插入优化。

您可以配置数据库以设置UUID值。有关更多信息,请参见此处


6

您试图持久化UUID类型的对象,但这不是Hibernate注释实体。因此,Hibernate想将其序列化为字节数组(blob类型)。这就是为什么您会收到“类型为bytea的表达式”的消息。

您可以将UUID存储为数据库中的blob(不太优雅),或提供自定义序列化程序(很麻烦),或手动转换该对象。 UUID类具有fromString和toString方法,因此我建议将其存储为字符串。


2
谢谢你的回答,Lech。但是根据http://opensource.atlassian.com/projects/hibernate/browse/HHH-3579,Hibernate应该可以像处理其他类型一样处理UUID类型。而将uuids存储在char类型中从性能角度来看是有害的。还有一个教程项目http://opensource.atlassian.com/projects/hibernate/browse/HHH-4093,展示了如何使用UUID而不需要冗余的解决方案。 - forker
该死,我刚刚注意到我提到的票据中的补丁未应用于 hibernate 3.6.0 发布版!问题已解决。 - forker
而且,这些补丁不适用于 Hibernate 3.6.0 源代码,因为该版本进行了一些重构。 - forker
通常在数据库中将 ID 存储为字符串并不是一个好主意。因为与数字类型相比,它消耗更多字节,受编码影响,并且会按文本排序。 - bvdb
除了我对使用字符串作为键的批评之外:感谢您解释了实际出了什么问题。(点赞) - bvdb

3
如其他人所提到的,解决此问题的方法是添加一个 @Type(type = "pg-uuid") 注释。然而,这种类型与其他供应商的 UUID 类型不兼容,因此这会将您的 Hibernate 类绑定到 Postgres。为了避免这种情况,可以在运行时插入此注释。以下适用于 Hibernate 4.3.7。
首先,在创建 Configuration 类的实例后的第一步中,需要插入自定义元数据提供程序以插入注释。
// Perform some test to verify that the current database is Postgres.
if (connectionString.startsWith("jdbc:postgresql:")) {
    // Replace the metadata provider with our custom metadata provider.
    MetadataProviderInjector reflectionManager = MetadataProviderInjector)cfg.getReflectionManager();
    reflectionManager.setMetadataProvider(new UUIDTypeInsertingMetadataProvider(reflectionManager.getMetadataProvider()));
}

这个自定义元数据提供程序会查找类型为UUID的字段和方法。如果发现其中一个,它将插入一个org.hibernate.annotations.Type注释的实例,说明该类型应该是"pg-uuid"

package nl.gmt.data;

import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.common.reflection.AnnotationReader;
import org.hibernate.annotations.common.reflection.MetadataProvider;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

class UUIDTypeInsertingMetadataProvider implements MetadataProvider {
    private final Map<AnnotatedElement, AnnotationReader> cache = new HashMap<>();
    private final MetadataProvider delegate;

    public UUIDTypeInsertingMetadataProvider(MetadataProvider delegate) {
        this.delegate = delegate;
    }

    @Override
    public Map<Object, Object> getDefaults() {
        return delegate.getDefaults();
    }

    @Override
    public AnnotationReader getAnnotationReader(AnnotatedElement annotatedElement) {
        // This method is called a lot of times on the same element, so annotation
        // readers are cached. We only cache our readers because the provider
        // we delegate to also caches them.

        AnnotationReader reader = cache.get(annotatedElement);
        if (reader != null) {
            return reader;
        }

        reader = delegate.getAnnotationReader(annotatedElement);

        // If this element is a method that returns a UUID, or a field of type UUID,
        // wrap the returned reader in a new reader that inserts the "pg-uuid" Type
        // annotation.

        boolean isUuid = false;
        if (annotatedElement instanceof Method) {
            isUuid = ((Method)annotatedElement).getReturnType() == UUID.class;
        } else if (annotatedElement instanceof Field) {
            isUuid = ((Field)annotatedElement).getType() == UUID.class;
        }

        if (isUuid) {
            reader = new UUIDTypeInserter(reader);
            cache.put(annotatedElement, reader);
        }

        return reader;
    }

    private static class UUIDTypeInserter implements AnnotationReader {
        private static final Type INSTANCE = new Type() {
            @Override
            public Class<? extends Annotation> annotationType() {
                return Type.class;
            }

            @Override
            public String type() {
                return "pg-uuid";
            }

            @Override
            public Parameter[] parameters() {
                return new Parameter[0];
            }
        };

        private final AnnotationReader delegate;

        public UUIDTypeInserter(AnnotationReader delegate) {
            this.delegate = delegate;
        }

        @Override
        @SuppressWarnings("unchecked")
        public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
            if (annotationType == Type.class) {
                return (T)INSTANCE;
            }

            return delegate.getAnnotation(annotationType);
        }

        @Override
        public <T extends Annotation> boolean isAnnotationPresent(Class<T> annotationType) {
            return annotationType == Type.class || delegate.isAnnotationPresent(annotationType);
        }

        @Override
        public Annotation[] getAnnotations() {
            Annotation[] annotations = delegate.getAnnotations();
            Annotation[] result = Arrays.copyOf(annotations, annotations.length + 1);
            result[result.length - 1] = INSTANCE;
            return result;
        }
    }
}

1
新的 Jakarta Persistence 3.1(又称 JPA 3.1,是 Jakarta EE 10 的一部分)添加了 UUID 主键生成策略,并支持将 UUID 作为主键和基本类型。包括 EclipseLink 4.x 和 Hibernate 6.2/6.3.x 在内的 JPA 提供者已经支持了这些功能。
在代码中简单地使用 Java 的 UUID 类型,并在 Postgres 数据库中使用 uuid 数据类型即可。
@Entity
public class Person {
    @Id
    @Column(name = "id", nullable = false)
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(...)
    private UUID apiKey;

}


检查一下我演示使用JPA 3.1的例子,其中包括在Glassfish容器中使用EclipseLink和在Java SE中使用Hibernate的链接1链接2

0

针对不使用JPA的人的解决方案。

之前:

<property name="testId" >
        <column name="test_id"  sql-type="uuid"  not-null="true"/>
</property>

之后:

<property name="testId" column="test_id" type="org.hibernate.type.PostgresUUIDType">
</property>

0

我在使用Sprint Data和Postgres中的jsonb时遇到了类似的问题。感谢Sri提供的解决方案!

在模型中,将以下内容替换:

@Type(type="pg-uuid")

使用

@Type(type="org.hibernate.type.PostgresUUIDType")

已解决使用 @SpringBootTest 运行 JUnit 测试时出现的问题。

在实体类中的示例(Kotlin):

@Type(type="org.hibernate.type.PostgresUUIDType")
@Column(
    nullable = false,
    unique = true,
    updatable = false,
    columnDefinition = "CHAR(36)"
)
var uuid: UUID = UUID.randomUUID()

0
对于新的Hibernate,我使用了
@JdbcType(UUIDJdbcType.class)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接