spring / hibernate: 为 Id 列自动生成 UUID
spring / hibernate: Generate UUID automatically for Id column
我正在尝试使用 Spring、hibernate/JPA 和 PostgreSQL 数据库来坚持一个简单的 class。
table的ID
列是一个UUID,我想在代码中生成,而不是在数据库中。
这应该是 straightforward,因为 hibernate 和 postgres 对 UUID 有很好的支持。
每次我创建一个新实例并用save()
写入它时,我得到以下错误:
o.h.j.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"; SQL statement: INSERT INTO DOODAHS (fieldA, fieldB) VALUES , ) ...
此错误表明它期望在插入行时自动填充 ID 列(使用一些默认值)。
class 看起来像这样:
@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
注释class
- 用
@javax.persistence.Id
替换spring@Id
注解
我看到了有用的答案 here, and ,但 none 到目前为止还有效。
注意,持久性由 class 处理,如下所示:
@org.springframework.stereotype.Repository
public interface DoodahRepository extends CrudRepository<Doodah, UUID> ;
table 的 DDL 是这样的:
CREATE TABLE DOODAHS(id UUID not null, fieldA VARCHAR(10), fieldB VARCHAR(10));
更新
感谢 Sve Kamenska,在他的帮助下我终于让它运行起来了。我放弃了 JPA 方法 - 请注意,我们使用的是 R2DBC,而不是 JDBC,因此答案并没有立即奏效。多个来源 (, here, here, here, here and ) 表明 R2DBC 没有自动生成 ID。所以你必须添加一个回调 Bean 来手动设置你的 Id
。
我更新了 class 如下:
@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"....
更新
至少有两种类型的 Spring 数据:JPA
和 JDBC
。
出现此问题是因为您混合了其中的 2 个。
因此,为了修复,有 2 个解决方案。
解决方案 1 - 仅使用 Spring 数据 JDBC.
Pom.xml依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
生成 ID。
Spring 数据 JDBC 假定 ID 是在数据库级别生成的(就像我们已经从日志中得出的那样)。如果您尝试使用 pre-defined 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 数据 JPA
Pom.xml依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
生成 ID。
实际上,您可以在这里使用带有 UUID auto-generation 的方法,就像您最初想做的那样
在 class-level
上使用 javax.persistence @Entity
注释而不是 springdata @Table
和 使用 @javax.persistence.Id
和 @javax.persistence.GeneratedValue
并在 id-field.
上使用所有默认值
@javax.persistence.Id
@javax.persistence.GeneratedValue
私人 UUID id;
其他说明:
- 不需要指定生成器和策略,因为它将根据
id
字段的类型生成(在本例中为 UUID
)。
- 也不需要指定
Column(nullable = false, unique = true)
,因为放置 @Id
注释已经假设了这些约束。
更新前的初步回答
主要问题:如何保存实体?由于 id-generation 由 JPA 提供程序处理,在本例中为 Hibernate。它在 em
或 repository
的保存方法期间完成。为了创建实体和 ID,Hibernate 正在寻找 javax.persistence
注释,而你有 Spring-specific,所以我在想你如何保存它们。
还有一个问题:您提供的错误 INSERT INTO DOODAHS (fieldA, fieldB) VALUES ,
显示 insert-query 中根本没有 id
字段。您是否只是简化了 error-message 并从中删除了 ID?或者这是原始错误,您的代码甚至没有“看到”字段 ID?在那种情况下,问题与 id-generation 无关,而是与为什么您的代码看不到此字段的问题有关。
我正在尝试使用 Spring、hibernate/JPA 和 PostgreSQL 数据库来坚持一个简单的 class。
table的ID
列是一个UUID,我想在代码中生成,而不是在数据库中。
这应该是 straightforward,因为 hibernate 和 postgres 对 UUID 有很好的支持。
每次我创建一个新实例并用save()
写入它时,我得到以下错误:
o.h.j.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"; SQL statement: INSERT INTO DOODAHS (fieldA, fieldB) VALUES , ) ...
此错误表明它期望在插入行时自动填充 ID 列(使用一些默认值)。
class 看起来像这样:
@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
注释class - 用
@javax.persistence.Id
替换spring
@Id
注解
我看到了有用的答案 here,
注意,持久性由 class 处理,如下所示:
@org.springframework.stereotype.Repository
public interface DoodahRepository extends CrudRepository<Doodah, UUID> ;
table 的 DDL 是这样的:
CREATE TABLE DOODAHS(id UUID not null, fieldA VARCHAR(10), fieldB VARCHAR(10));
更新
感谢 Sve Kamenska,在他的帮助下我终于让它运行起来了。我放弃了 JPA 方法 - 请注意,我们使用的是 R2DBC,而不是 JDBC,因此答案并没有立即奏效。多个来源 (Id
。
我更新了 class 如下:
@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"....
更新
至少有两种类型的 Spring 数据:JPA
和 JDBC
。
出现此问题是因为您混合了其中的 2 个。
因此,为了修复,有 2 个解决方案。
解决方案 1 - 仅使用 Spring 数据 JDBC.
Pom.xml依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency>
生成 ID。
Spring 数据 JDBC 假定 ID 是在数据库级别生成的(就像我们已经从日志中得出的那样)。如果您尝试使用 pre-defined 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 数据 JPA
Pom.xml依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
生成 ID。
实际上,您可以在这里使用带有 UUID auto-generation 的方法,就像您最初想做的那样
在 class-level
上使用javax.persistence @Entity
注释而不是springdata @Table
和 使用
上使用所有默认值@javax.persistence.Id
和@javax.persistence.GeneratedValue
并在 id-field.@javax.persistence.Id
@javax.persistence.GeneratedValue
私人 UUID id;
其他说明:
- 不需要指定生成器和策略,因为它将根据
id
字段的类型生成(在本例中为UUID
)。 - 也不需要指定
Column(nullable = false, unique = true)
,因为放置@Id
注释已经假设了这些约束。
更新前的初步回答
主要问题:如何保存实体?由于 id-generation 由 JPA 提供程序处理,在本例中为 Hibernate。它在 em
或 repository
的保存方法期间完成。为了创建实体和 ID,Hibernate 正在寻找 javax.persistence
注释,而你有 Spring-specific,所以我在想你如何保存它们。
还有一个问题:您提供的错误 INSERT INTO DOODAHS (fieldA, fieldB) VALUES ,
显示 insert-query 中根本没有 id
字段。您是否只是简化了 error-message 并从中删除了 ID?或者这是原始错误,您的代码甚至没有“看到”字段 ID?在那种情况下,问题与 id-generation 无关,而是与为什么您的代码看不到此字段的问题有关。