【问题标题】:JPA and Hibernate, OneToMany relationship with a composite key using UUIDJPA 和 Hibernate、OneToMany 关系与使用 UUID 的复合键
【发布时间】:2023-08-30 13:35:01
【问题描述】:

我正在使用带有 JPA 和 Hibernate 的 SpringBoot。

我有两个实体:书和作者。一本书可以有多个作者。所以我需要一个 OneToMany 关系。

进入 Author 表,我的想法是使用复合键(book_id 和一个内部字段,例如 field_a)

我实现了这些类:

@Entity
@Table(name = "book")
public class Book extends MyBaseEntity {

    @Id
    @Column(name = "id")
    @Type(type = "uuid-char")
    private UUID uuid = UUID.randomUUID();

    @Column(name = "name", nullable = false)
    private String name;

    @OneToMany(mappedBy = "book")
    private List<Author> authors = new ArrayList<>();

}

@Entity
@Table(name = "author")
public class Author extends MyBaseEntity {

    @EmbeddedId
    private CustomID id;

    @ManyToOne()
    @MapsId("bookId")
    @JoinColumn(name = "book_id")
    private Book book;

    @Column(name = "field_a", nullable = false)
    @MapsId("fieldA")
    @Type(type = "uuid-char")
    private UUID fieldA;

}


@Embeddable
public class CustomID implements Serializable {

    @Column(name = "book_id")
    private UUID bookId;

    @Column(name = "field_a")
    private UUID fieldA;

    public CustomID() { }

    public CustomID(UUID bookId, UUID fieldA) {
        this.bookId = bookId;
        this.fieldA = fieldA;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;

        if (o == null || getClass() != o.getClass())
            return false;

        ShareID that = (CustomID) o;
        return Objects.equals(bookId, that.bookId) &&
                Objects.equals(fieldA, that.fieldA);
    }

    @Override
    public int hashCode() {
        return Objects.hash(bookId, fieldA);
    }
}

当我尝试执行我的项目时(我正在使用属性spring.jpa.hibernate.ddl-auto=create 生成数据库)我收到此错误:

ERROR SpringApplication-reportFailure():837 - [ Application run failed ]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Unknown entity name: java.util.UUID 

有什么建议吗?

如果我尝试删除组合键的使用效果很好,那么问题可能与组合键的实现或使用有关。

【问题讨论】:

  • 与您的实际问题无关的问题:您真的希望Book 有多个Author,但一个Author 只能与一个Book 相关吗?
  • 是的,可能我没有使用最合适的例子......但在我的真实场景中,它的关系是正确的
  • 只是问一下,因为从实体结构来看,您似乎采用了有效的多对多关系设置并尝试将其转换为一对多设置。从实体设计的角度来看,复合键对我来说没有多大意义,为什么不直接映射到作者实体中的书?
  • 不过也可能是因为我用了太多的Spring Data抽象,太久没用JPA了^^
  • 问题是 UUID 'fieldA' 有两个映射。 MapsId 用于关系映射,fieldA 不是,它告诉 JPA 使用此引用实体中的主键来设置您指向它的嵌入式 ID - 'fieldA' 中的值。只需从 Author 类中彻底删除它,并在需要使用该 UUID 时使用 author.id.fieldA。

标签: java spring-boot hibernate jpa


【解决方案1】:

使用带有 Author as 的 ID 类:

@Entity
@Table(name = "author")
@IdClass(CustomID.class)
public class Author extends MyBaseEntity {

    @Id
    @ManyToOne()
    @JoinColumn(name = "book_id")
    private Book book;

    @ID
    @Column(name = "field_a", nullable = false)
    @Type(type = "uuid-char")
    private UUID fieldA;

}

public class CustomID implements Serializable {
    //these match the names of the property marked with @Id in Author
    private UUID book;
    private UUID fieldA;
}

或者像您已经完成的那样为您的实体定义基本字段的嵌入:

@Entity
@Table(name = "author")
public class Author extends MyBaseEntity {

    @EmbeddedId
    private CustomID id;

    @ManyToOne()
    @MapsId("bookId")
    @JoinColumn(name = "book_id")
    private Book book;
}

【讨论】:

  • 感谢您的回答,您提出的两个解决方案是否还有一些其他方面需要考虑?例如,是否有更好的与性能相关的解决方案,或者更清洁的解决方案或其他?
  • 这是您必须根据您的用例、缓存以及您的提供程序处理获取的方式进行调查的内容。 EmbeddedID 意味着您可以访问 author.id.bookId 而无需使用 author.book.id 进行查询 - 所以这取决于您的提供者如何处理它以及它是否执行内部连接,并且它可能更容易访问书 id 而不必获取书本身。否则,我不喜欢可嵌入的,但这是个人偏好,没有真正充分的理由(大多数 ORM 提供商早就处理过遗留问题)。
  • 使用你的第一个解决方案,进入数据库,关于 book_id 列我看到这样的值:éÄH&amp;MmRàù¿Û 为什么?是否可以有相关实体 Book 的 id 值?
  • 在这两种解决方案中 Book_Id 都是外键,因此它包含的任何值都必须来自 book 表 ID 列。我不知道你为什么会从 UUID 中看到类似的值。