spring / hibernate:为Id列自动生成UUID



我正在尝试使用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

我尝试过的事情:

  1. @javax.persistence.Id注释字段(除了现有的spring Id)
  2. @org.hibernate.annotations.Type(type = "pg-uuid")
  3. 注释字段
  4. 自己创建UUID -结果在Spring抱怨它找不到具有该id的行。
  5. 指定strategy = "org.hibernate.id.UUIDGenerator"
  6. @Entity注释类
  7. 将弹簧@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 = nullisNew = true)被传递给save()方法时,回调方法被调用,它设置id。

最初我尝试使用BeforeSaveCallback,但它被称为太晚的过程中,导致以下异常:

JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"....

更新至少有2种类型的Spring Data:JPAJDBC

问题的发生是因为你把它们两个混合在一起了。

所以,为了解决这个问题,有两个解决方案。

解决方案1-只使用Spring Data JDBC.

  1. Pom.xml依赖

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
    
  2. 生成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

  1. Pom.xml依赖

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
  2. 生成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。这是在emrepository的保存方法期间完成的。为了创建实体和id, Hibernate正在寻找javax.persistence注释,而您有特定于spring的注释,所以我正在徘徊如何保存它们。

这里还有一个问题:您提供的INSERT INTO DOODAHS (fieldA, fieldB) VALUES $1, $2错误表明插入查询中根本没有id字段。您只是简化了错误消息并从中删除了ID吗?或者这是原始错误,你的代码甚至没有看到;字段ID吗?在这种情况下,问题与id生成无关,而是与为什么你的代码看不到这个字段有关。

最新更新