【问题标题】:Hibernate: OneToOne: two fields of the same typeHibernate:OneToOne:两个相同类型的字段
【发布时间】:2021-01-02 19:48:39
【问题描述】:

假设我有一个Person 实体和一个Animal 实体; Person 可以有 两个 最喜欢的动物,Animal 只能有 一个 Person其他人喜欢/看到那只动物)。这是一个@OneToOne 映射:用于Person 中的两个Animal 字段和Animal 中的两个Person 字段。然而,在AnimalfirstPerson 中应该==secondPerson。有没有办法在 Animal 类中只使用一个 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;
...

【问题讨论】:

  • 如果正如您的问题所述,这两个引用都应引用相同的 Person,为什么您希望将两个 OneToOne 都设为双向?
  • @SternK 那还有什么办法呢?
  • “一个人可以有 两个 最喜欢的动物 [...] 这是一个 @OneToOne 映射” → 这显然是一个矛盾。但是,如果您不明白为什么,请考虑一下:映射反映的是 实体 之间的关系,而不是特定字段之间的关系。如果一个 Person多个(“许多”)Animals 相关,即如果SELECT * FROM ANIMAL WHERE FIRSTPERSON = xyz 可能(通常)返回“多”行,则PersonAnimal 之间的关系是一对多。 (我不会解释如何反映 @OneToMany 关系,因为你已经评论过你知道如何。)
  • 但在我的示例中,SELECT * FROM ANIMAL WHERE FIRSTPERSON = xyz 将返回 1 行。比如说,animals 表将有两列 - firstpersonsecondperson。如果 ID 为 4 的特定动物有一个人 57 作为 firstperson,那么没有其他动物可以拥有这个人作为 firstperson。同时57 人将在firstanimal 列中拥有动物4
  • @walen 并且没有其他人会拥有该动物,因为 firstanimalselect * from persons where firstanimal = 4 将返回一行 id = 57,select * from animals where firstperson = 57 将返回一行 id = 4

标签: java hibernate jpa inheritance orm


【解决方案1】:

您可能希望在这种情况下使用 @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 个人拥有,您可以使用代码逻辑检查它们。

【讨论】:

  • 我知道如何使用OneToMany。问题的重点是如何使用OneToOne
【解决方案2】:

我认为没有一种简单的方法可以使用OneToOne 实现这种双向映射(正如其他人指出的那样,这并不是真正的一对一关联)。即使有,我认为鉴于域模型的要求,您的模型将不具有可读性或易于理解:当您说“动物只能有一个喜欢它们的人”时,我们不应该我们不希望在 Animal 类中有两个 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;

    ...
}

【讨论】:

    【解决方案3】:

    简短的回答是,JPA 和 Hibernate 并非旨在满足您的要求。无法通过注解和映射来配置它。

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

    如果您映射了 2 个不同的一对一字段,您可以在 Animal 内部添加一个临时字段,该字段检查第一个字段,然后检查另一个字段。如果第一个字段不为空,则返回它。否则,返回第二个字段(不需要进行第二次空值检查)。

    如果你想强制一个 Animal 只被一个人喜欢,也没有办法通过标准的 JPA 注释或通过特定于 hibernate 的注释来表示这一点。可以添加一些检查约束、触发器和索引,以保证每个 Animal 的“like”的唯一性,但您无法通过任何注释获得开箱即用的内容。

    【讨论】:

      【解决方案4】:

      就像每个人都说过的那样 - 如果您对动物有一个人,但对一个人有多个动物,这不可能是一对一的关系。这是一对一的关系。

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

      或者,如果设置了第一人称,您可以强制第二人称为空 - 但然后将其设为应有的多对一关系;)

      【讨论】:

        【解决方案5】:

        数据库或 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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-08-20
          • 1970-01-01
          • 2020-04-23
          • 1970-01-01
          • 2011-08-05
          • 2023-04-11
          • 1970-01-01
          • 2016-11-10
          相关资源
          最近更新 更多