Hibernate: OneToOne: 同一类型的两个字段

Hibernate: OneToOne: two fields of the same type

假设我有一个 Person 实体和一个 Animal 实体;一个Person可以有两个个最喜欢的动物,一个Animal只能有一个 Person喜欢它们(一个人喜欢一种动物使得其他人不再可能 like/see 该动物)。它是一个 @OneToOne 映射:用于 Person 中的两个 Animal 字段和 Animal 中的两个 Person 字段。但是,在AnimalfirstPerson应该==secondPerson。有没有办法在 Animal class?

中仅使用一个 Person 字段来执行以下操作

Person.java:

@Entity
@SequenceGenerator(name="PERSON_SEQ", sequenceName="person_sequence")
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="PERSON_SEQ")
    private Long id;

    @Column
    private String name;

    public Person()  {}

    @OneToOne
    @JoinColumn(name = "firstAnimal")
    private Animal firstAnimal;
    @OneToOne
    @JoinColumn(name = "secondAnimal")
    private Animal secondAnimal;

    //getters and setters

Animal.java:

@Entity
@SequenceGenerator(name="PRIVATE_SEQ", sequenceName="private_sequence")
public class Animal {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="PRIVATE_SEQ")
    private Long id;

    @Column
    private String name;

   @OneToOne(mappedBy = "firstAnimal")
   private Person firstPerson;

   @OneToOne(mappedBy = "secondAnimal")
   private Person secondPerson;
...

您可能希望在这种情况下使用 @OneToMany / @ManyToOne 来获得永久解决方案。

Person.java:

@Entity
@Table(name = "...")
public class Person {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
    @Column(name="person_id")
    private Long id;

    @Column(name="...")
    private String name;

    @OneToMany(mappedBy = "owner", cascade = CascadeType.?)
    private List<Animal> animals;
    
}

Animal.java:

@Entity
@Table(name = "...")
public class Animal {
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
    @Column(name = "...")
    private Long id;

    @Column(name = "...")
    private String name;

    @ManyToOne
    @JoinColumn(name="columnName", referencedColumnName="person_id")
    private Person owner;

}

对于进一步的限制,例如一个人最多只能拥有 2 只喜欢的动物,或者一只动物只能由 1 个人拥有,您可以使用代码逻辑检查它们。

就像每个人都说的那样 - 如果一个人对一只动物,但多个动物对一个人,那不可能是 one-to-one 关系。那是two-to-one关系。

如果动物的第一人称是a,第二人称是a,那么两个人a的连接也都用上了。

或者,如果设置了第一人称,您可以强制第二人称为 null - 但随后只需将其设为多对一关系即可;)

我不认为有一种简单的方法可以用 OneToOne 实现这种双向映射(正如其他人指出的那样,这并不是真正的 one-to-one 关联)。即使有,我也会争辩说,鉴于域模型要求,您的模型将不可读或不易理解:当您说 “一个动物只能有一个喜欢它们的人”,我们不应该期望 Animal class 中有两个 Person 字段,我们应该期望只有一个。

您可以放弃双向映射并为 Person 中的每个 Animal 选择一个简单的单向关联:

@Entity
@SequenceGenerator(name="PERSON_SEQ", sequenceName="person_sequence")
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="PERSON_SEQ")
    private Long id;

    @Column
    private String name;

    public Person()  {}

    @OneToOne
    @JoinColumn(name = "firstAnimal")
    private Animal firstAnimal;

    @OneToOne
    @JoinColumn(name = "secondAnimal")
    private Animal secondAnimal;

    ...
}

@Entity
@SequenceGenerator(name="PRIVATE_SEQ", sequenceName="private_sequence")
public class Animal {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="PRIVATE_SEQ")
    private Long id;

    @Column
    private String name;

    @OneToOne
    @JoinColumn(name = "person")
    private Person person;

    ...
}

简短的回答是,JPA 和 Hibernate 并不是为满足您的要求而设计的。无法通过注释和映射进行配置。

较长的答案是,您可以使用瞬态字段和数据库配置的组合来获得您正在寻找的行为...

如果您映射了 2 个不同的 one-to-one 字段,您可以在 Animal 中添加一个瞬态字段,首先检查一个字段,然后再检查另一个字段。如果第一个字段是not-null,return它。否则,return第二个字段(不需要第二个null-check)。

如果你想强制一个 Animal 只被一个人喜欢,也没有办法通过标准 JPA 注释或通过 hibernate-specific 注释来表示这一点。可以添加一些 check-constraints、触发器和索引来保证每个 Animal 的“喜欢”的唯一性,但您不会通过任何注释获得 out-of-the-box。

数据库或 OOP 范式中的设计根本不可扩展。在现实领域中,数据库中的列数或实体中的字段数与一个人拥有的动物数一样多(最喜欢)。

在数据库中,您甚至不遵守 first normal formal。同样在基数方面,你总是有 0、1 或 n。没有2这个东西,2就是n.

您可以重新设计您的模型,考虑将人与带有注释 @OneToMany

的动物列表相关联

在 Kotlin 中它将是:

@OneToMany(cascade = [CascadeType.ALL])
@JoinColumn(name = "animal_id", referencedColumnName = "animal_id")
val animals: List<Animal>

另一方面,您可以为表示类型的动物添加 enum

enum class TypeAnimal {
        FAVOURITE
}

GL