我无法让 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>
可以通过在UUID上添加以下注释来解决:
import org.hibernate.annotations.Type;
...
@Type(type="pg-uuid")
private java.util.UUID itemUuid;
至于为什么 Hibernate 不只是将其设为默认设置,我无法告诉你......
更新: 使用 createNativeQuery 方法打开具有 UUID 字段的对象似乎仍然存在问题。幸运的是,到目前为止,createQuery 方法对我来说效果很好。
现在您还可以使用 java.util.UUID 提供的 UUID 类,该类由 Hibernate 映射到 Postgres 的 uuid 数据类型,而从数据库读取/写入时无需任何转换。
@Id
@GeneratedValue
private UUID id;
默认情况下生成的值为 auto,这允许您的 JVM 定义 UUID。这也允许 hibernate 使用批量插入优化。
您可以配置数据库来设置UUID值。更多信息可以在这里找到https://vladmihalcea.com/uuid-identifier-jpa-hibernate/
您尝试保留 UUID 类型的对象,该对象不是带有 hibernate 注解的实体。所以hibernate想将其序列化为字节数组(blob类型)。这就是为什么您会收到此消息“bytea 类型的表达式”。
您可以将 UUID 作为 blob 存储在数据库中(不优雅),或者提供自定义序列化程序(大量工作)或手动转换该对象。 UUID 类具有 fromString 和 toString 方法,因此我会将其存储为 String。
正如其他人提到的,此问题的解决方案是添加
@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;
}
}
}
针对不使用 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>
在 postgres 中使用 Sprint 数据和 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()
对于我使用的新休眠
@JdbcType(UUIDJdbcType.class)
新的 Jakarta Persistence 3.1(又名 JPA 3.1,Jakarta EE 10 的一部分)添加了 UUID 主键生成策略,并支持
UUID
作为主键和基本类型。
只需在代码中使用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;
}