Hibernate: OneToOne: 同一类型的两个字段
Hibernate: OneToOne: two fields of the same type
假设我有一个 Person
实体和一个 Animal
实体;一个Person
可以有两个个最喜欢的动物,一个Animal
只能有一个 Person
喜欢它们(一个人喜欢一种动物使得其他人不再可能 like/see 该动物)。它是一个 @OneToOne
映射:用于 Person
中的两个 Animal
字段和 Animal
中的两个 Person
字段。但是,在Animal
中firstPerson
应该==
到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
假设我有一个 Person
实体和一个 Animal
实体;一个Person
可以有两个个最喜欢的动物,一个Animal
只能有一个 Person
喜欢它们(一个人喜欢一种动物使得其他人不再可能 like/see 该动物)。它是一个 @OneToOne
映射:用于 Person
中的两个 Animal
字段和 Animal
中的两个 Person
字段。但是,在Animal
中firstPerson
应该==
到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