我正在尝试使用hibernate/JPA和PostgreSQL数据库持久化一个简单的类。
表的ID
列是一个UUID,我想在代码中生成,而不是在数据库中生成。
这应该很简单,因为hibernate和postgres对uid有很好的支持。
每次我创建一个新实例并使用save()
写入它时,我得到以下错误:
o.h.j.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"; SQL statement: INSERT INTO DOODAHS (fieldA, fieldB) VALUES $1, $2) ...
这个错误表明它期望在插入一行时自动填充ID列(使用一些默认值)。
类是这样的:
@lombok.Data
@lombok.AllArgsConstructor
@org.springframework.data.relational.core.mapping.Table("doodahs")
public class Doodah {
@org.springframework.data.annotation.Id
@javax.persistence.GeneratedValue(generator = "UUID")
@org.hibernate.annotations.GenericGenerator(name="UUID", strategy = "uuid2")
@javax.persistence.Column(nullable = false, unique = true)
private UUID id;
//... other fields
我尝试过的事情:
- 用
@javax.persistence.Id
注释字段(除了现有的spring Id) - 用
@org.hibernate.annotations.Type(type = "pg-uuid")
注释字段 - 自己创建UUID -结果在Spring抱怨它找不到具有该id的行。
- 指定
strategy = "org.hibernate.id.UUIDGenerator"
- 用
@Entity
注释类 - 将弹簧
@Id
注释替换为@javax.persistence.Id
我在这里,这里和这里看到了有用的答案,但到目前为止都没有成功。
注意,持久性是由一个类处理的,看起来像这样:
@org.springframework.stereotype.Repository
public interface DoodahRepository extends CrudRepository<Doodah, UUID> ;
表的DDL如下所示:
CREATE TABLE DOODAHS(id UUID not null, fieldA VARCHAR(10), fieldB VARCHAR(10));
更新感谢Sve Kamenska,在他的帮助下,我终于把它弄工作了。我放弃了JPA方法——注意,我们使用的是R2DBC,而不是JDBC,所以答案并没有立即奏效。几个来源(这里、这里、这里、这里、这里和这里)表明,R2DBC没有自动生成Id。所以你必须添加一个回调Bean来手动设置你的Id
。
我更新类如下:
@Table("doodahs")
public class Doodah {
@org.springframework.data.annotation.Id
private UUID id;
我还添加了一个Bean如下:
@Bean
BeforeConvertCallback<Doodah> beforeConvertCallback() {
return (d, row, table) -> {
if (d.getId() == null){
d.id = UUID.randomUUID();
}
return Mono.just(d);
};
}
当一个新对象(id = null
和isNew = true
)被传递给save()
方法时,回调方法被调用,它设置id。
最初我尝试使用BeforeSaveCallback
,但它被称为太晚的过程中,导致以下异常:
JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"....
更新至少有2种类型的Spring Data:JPA
和JDBC
。
问题的发生是因为你把它们两个混合在一起了。
所以,为了解决这个问题,有两个解决方案。
解决方案1-只使用Spring Data JDBC.
-
Pom.xml依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency>
-
生成ID。
Spring Data JDBC假定ID是在数据库级别生成的(就像我们已经从日志中发现的那样)。如果您尝试保存具有预定义id的实体,Spring将假定它是一个存在的实体,并尝试在数据库中找到它并进行更新。这就是为什么你在尝试#3中得到这个错误。
为了生成UUID,可以:
-
把它留给DB(看起来Postgre允许这样做)
-
或填写
BeforeSaveCallback
(更多细节在这里https://spring.io/blog/2021/09/09/spring-data-jdbc-how-to-use-custom-id-generation)@Bean BeforeSaveCallback<Doodah> beforeSaveCallback() { return (doodah, mutableAggregateChange) -> { if (doodah.id == null) { doodah.id = UUID.randomUUID(); } return doodah; }; }
解决方案2-只使用Spring Data JPA
-
Pom.xml依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
-
生成ID。
这里你可以使用UUID自动生成的方法,就像你最初想做的那样
-
在类级别使用
javax.persistence @Entity
注释而不是springdata @Table
注释 -
和使用
@javax.persistence.Id
和@javax.persistence.GeneratedValue
,所有id-field的默认值@javax.persistence.Id
@javax.persistence.GeneratedValue
private UUID id;
其他笔记:
- 不需要指定生成器和策略,因为它将根据
id
字段的类型(在本例中为UUID
)生成。 - 也不需要
Column(nullable = false, unique = true)
规范,因为放置@Id
注释已经假定了这些约束。
更新前的初始答案
主要问题是:如何保存实体?由于id生成是由JPA提供程序处理的,在本例中是Hibernate。这是在em
或repository
的保存方法期间完成的。为了创建实体和id, Hibernate正在寻找javax.persistence
注释,而您有特定于spring的注释,所以我正在徘徊如何保存它们。
这里还有一个问题:您提供的INSERT INTO DOODAHS (fieldA, fieldB) VALUES $1, $2
错误表明插入查询中根本没有id
字段。您只是简化了错误消息并从中删除了ID吗?或者这是原始错误,你的代码甚至没有看到;字段ID吗?在这种情况下,问题与id生成无关,而是与为什么你的代码看不到这个字段有关。