Java 双向 JPA OneToMany/ManyToOne 关联中的“关联的反面”是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2584521/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is “the inverse side of the association” in a bidirectional JPA OneToMany/ManyToOne association?
提问by Behrang Saeedzadeh
In the example section of the @OneToMany
JPA annotation reference:
在@OneToMany
JPA 注释参考的示例部分:
Example 1-59 @OneToMany - Customer Class With Generics
示例 1-59 @OneToMany - 带有泛型的客户类
@Entity
public class Customer implements Serializable {
...
@OneToMany(cascade=ALL, mappedBy="customer")
public Set<Order> getOrders() {
return orders;
}
...
}
Example 1-60 @ManyToOne - Order Class With Generics
示例 1-60 @ManyToOne - 带有泛型的订单类
@Entity
public class Order implements Serializable {
...
@ManyToOne
@JoinColumn(name="CUST_ID", nullable=false)
public Customer getCustomer() {
return customer;
}
...
}
It seems to me that the Customer
entity is the owner of the association. However, in the explanation for the mappedBy
attribute in the same document, it is written that:
在我看来,该Customer
实体是该协会的所有者。但是,在mappedBy
同一个文档中对属性的解释中,是这样写的:
if the relationship is bidirectional, then set the mappedBy element on the inverse (non-owning) side of the association to the name of the field or property that owns the relationship as Example 1-60 shows.
如果关系是双向的,则将关联反向(非拥有)侧的 mappingBy 元素设置为拥有该关系的字段或属性的名称,如示例 1-60 所示。
However, if I am not wrong, it looks like in the example, the mappedBy
is actually specified on the owning side of the association, rather than the non-owning side.
但是,如果我没有记错的话,在示例中,mappedBy
实际上是在关联的拥有方而不是非拥有方指定的。
So my question is basically:
所以我的问题基本上是:
In a bidirectional (one-to-many/many-to-one) association, which of the entities is the owner? How can we designate the One side as the owner? How can we designate the Many side as the owner?
What is meant by "the inverse side of the association"? How can we designate the One side as the inverse? How can we designate the Many side as the inverse?
在双向(一对多/多对一)关联中,哪个实体是所有者?我们如何指定一方为所有者?我们如何指定多方为所有者?
“关联的反面”是什么意思?我们如何将一侧指定为反面?我们如何将多边指定为逆?
采纳答案by Aaron Digulla
To understand this, you must take a step back. In OO, the customer owns the orders (orders are a list in the customer object). There can't be an order without a customer. So the customer seems to be the owner of the orders.
要理解这一点,您必须退后一步。在 OO 中,客户拥有订单(订单是客户对象中的一个列表)。没有客户就不可能有订单。所以客户似乎是订单的所有者。
But in the SQL world, one item will actually contain a pointer to the other. Since there is 1 customer for N orders, each order contains a foreign key to the customer it belongs to. This is the "connection" and this means the order "owns" (or literally contains) the connection (information). This is exactly the opposite from the OO/model world.
但在 SQL 世界中,一项实际上将包含指向另一项的指针。由于 N 个订单有 1 个客户,因此每个订单都包含其所属客户的外键。这是“连接”,这意味着订单“拥有”(或字面上包含)连接(信息)。这与面向对象/模型世界完全相反。
This may help to understand:
这可能有助于理解:
public class Customer {
// This field doesn't exist in the database
// It is simulated with a SQL query
// "OO speak": Customer owns the orders
private List<Order> orders;
}
public class Order {
// This field actually exists in the DB
// In a purely OO model, we could omit it
// "DB speak": Order contains a foreign key to customer
private Customer customer;
}
The inverse side is the OO "owner" of the object, in this case the customer. The customer has no columns in the table to store the orders, so you must tell it where in the order table it can save this data (which happens via mappedBy
).
反面是对象的 OO“所有者”,在这种情况下是客户。客户在表中没有用于存储订单的列,因此您必须告诉它可以在订单表中保存此数据的位置(通过 发生mappedBy
)。
Another common example are trees with nodes which can be both parents and children. In this case, the two fields are used in one class:
另一个常见的例子是具有可以是父节点和子节点的节点的树。在这种情况下,两个字段在一个类中使用:
public class Node {
// Again, this is managed by Hibernate.
// There is no matching column in the database.
@OneToMany(cascade = CascadeType.ALL) // mappedBy is only necessary when there are two fields with the type "Node"
private List<Node> children;
// This field exists in the database.
// For the OO model, it's not really necessary and in fact
// some XML implementations omit it to save memory.
// Of course, that limits your options to navigate the tree.
@ManyToOne
private Node parent;
}
This explains for the "foreign key" many-to-one design works. There is a second approach which uses another table to maintain the relations. That means, for our first example, you have three tables: The one with customers, the one with orders and a two-column table with pairs of primary keys (customerPK, orderPK).
这就解释了“外键”多对一的设计作品。还有第二种方法,它使用另一个表来维护关系。这意味着,对于我们的第一个示例,您有三个表:一个包含客户的表、一个包含订单的表和一个包含主键对(customerPK、orderPK)的两列表。
This approach is more flexible than the one above (it can easily handle one-to-one, many-to-one, one-to-many and even many-to-many). The price is that
这种方法比上面的方法更灵活(它可以轻松处理一对一、多对一、一对多甚至多对多)。价格是这样的
- it's a bit slower (having to maintain another table and joins uses three tables instead of just two),
- the join syntax is more complex (which can be tedious if you have to manually write many queries, for example when you try to debug something)
- it's more error prone because you can suddenly get too many or too few results when something goes wrong in the code which manages the connection table.
- 它有点慢(必须维护另一个表并且连接使用三个表而不是两个),
- 连接语法更复杂(如果您必须手动编写许多查询,例如当您尝试调试某些内容时,这可能会很乏味)
- 它更容易出错,因为当管理连接表的代码出现问题时,您可能会突然得到太多或太少的结果。
That's why I rarely recommend this approach.
这就是为什么我很少推荐这种方法。
回答by Venu
The entity which has the table with foreign key in the database is the owning entity and the other table, being pointed at, is the inverse entity.
数据库中具有外键表的实体是拥有实体,指向的另一个表是逆实体。
回答by Steve Jones
Unbelievably, in 3 years nobody has answered your excellent question with examples of both ways to map the relationship.
令人难以置信的是,在 3 年内没有人用两种方式来映射关系的例子回答了你的好问题。
As mentioned by others, the "owner" side contains the pointer (foreign key) in the database. You can designate either side as the owner, however, if you designate the One side as the owner, the relationship will not be bidirectional (the inverse aka "many" side will have no knowledge of its "owner"). This can be desirable for encapsulation/loose coupling:
正如其他人所提到的,“所有者”端包含数据库中的指针(外键)。您可以将任何一方指定为所有者,但是,如果您将 One 指定为所有者,则关系将不是双向的(反面也称为“多”方将不知道其“所有者”)。这对于封装/松散耦合是可取的:
// "One" Customer owns the associated orders by storing them in a customer_orders join table
public class Customer {
@OneToMany(cascade = CascadeType.ALL)
private List<Order> orders;
}
// if the Customer owns the orders using the customer_orders table,
// Order has no knowledge of its Customer
public class Order {
// @ManyToOne annotation has no "mappedBy" attribute to link bidirectionally
}
The only bidirectional mapping solution is to have the "many" side own its pointer to the "one", and use the @OneToMany "mappedBy" attribute. Without the "mappedBy" attribute Hibernate will expect a double mapping (the database would have both the join column and the join table, which is redundant (usually undesirable)).
唯一的双向映射解决方案是让“多”方拥有指向“一”的指针,并使用@OneToMany“mappedBy”属性。如果没有“mappedBy”属性,Hibernate 将期望双重映射(数据库将同时具有连接列和连接表,这是多余的(通常是不可取的))。
// "One" Customer as the inverse side of the relationship
public class Customer {
@OneToMany(cascade = CascadeType.ALL, mappedBy = "customer")
private List<Order> orders;
}
// "many" orders each own their pointer to a Customer
public class Order {
@ManyToOne
private Customer customer;
}
回答by HakunaMatata
For two Entity Classes Customer and Order , hibernate will create two tables.
对于两个实体类 Customer 和 Order ,hibernate 将创建两个表。
Possible Cases:
可能的情况:
mappedBy is not used in Customer.java and Order.java Class then->
At customer side a new table will be created[name = CUSTOMER_ORDER] which will keep mapping of CUSTOMER_ID and ORDER_ID. These are primary keys of Customer and Order Tables. At Order side an additional column is required to save the corresponding Customer_ID record mapping.
mappedBy is used in Customer.java [As given in problem statement] Now additional table[CUSTOMER_ORDER] is not created. Only one column in Order Table
mappedby is used in Order.java Now additional table will be created by hibernate.[name = CUSTOMER_ORDER] Order Table will not have additional column [Customer_ID ] for mapping.
在 Customer.java 和 Order.java 类中不使用 mappingBy then->
在客户端,将创建一个新表 [name = CUSTOMER_ORDER],它将保持 CUSTOMER_ID 和 ORDER_ID 的映射。这些是客户和订单表的主键。在订单端,需要一个额外的列来保存相应的 Customer_ID 记录映射。
在Customer.java 中使用了mappedBy [如问题陈述中给出的] 现在没有创建附加表[CUSTOMER_ORDER]。订单表中只有一列
在 Order.java 中使用了 mappingby 现在附加表将由 hibernate.[name = CUSTOMER_ORDER] 订单表将没有附加列 [Customer_ID ] 用于映射。
Any Side can be made Owner of the relationship. But its better to choose xxxToOne side.
任何一方都可以成为关系的所有者。但最好选择 xxxToOne 一侧。
Coding effect - > Only Owning side of entity can change relationship status. In below example BoyFriend class is owner of the relationship. even if Girlfriend wants to break-up , she can't.
编码效果 - > 只有实体的拥有方可以更改关系状态。在下面的示例中 BoyFriend 类是关系的所有者。就算女朋友想分手,她也做不到。
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "BoyFriend21")
public class BoyFriend21 {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Boy_ID")
@SequenceGenerator(name = "Boy_ID", sequenceName = "Boy_ID_SEQUENCER", initialValue = 10,allocationSize = 1)
private Integer id;
@Column(name = "BOY_NAME")
private String name;
@OneToOne(cascade = { CascadeType.ALL })
private GirlFriend21 girlFriend;
public BoyFriend21(String name) {
this.name = name;
}
public BoyFriend21() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BoyFriend21(String name, GirlFriend21 girlFriend) {
this.name = name;
this.girlFriend = girlFriend;
}
public GirlFriend21 getGirlFriend() {
return girlFriend;
}
public void setGirlFriend(GirlFriend21 girlFriend) {
this.girlFriend = girlFriend;
}
}
import org.hibernate.annotations.*;
import javax.persistence.*;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "GirlFriend21")
public class GirlFriend21 {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Girl_ID")
@SequenceGenerator(name = "Girl_ID", sequenceName = "Girl_ID_SEQUENCER", initialValue = 10,allocationSize = 1)
private Integer id;
@Column(name = "GIRL_NAME")
private String name;
@OneToOne(cascade = {CascadeType.ALL},mappedBy = "girlFriend")
private BoyFriend21 boyFriends = new BoyFriend21();
public GirlFriend21() {
}
public GirlFriend21(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public GirlFriend21(String name, BoyFriend21 boyFriends) {
this.name = name;
this.boyFriends = boyFriends;
}
public BoyFriend21 getBoyFriends() {
return boyFriends;
}
public void setBoyFriends(BoyFriend21 boyFriends) {
this.boyFriends = boyFriends;
}
}
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import java.util.Arrays;
public class Main578_DS {
public static void main(String[] args) {
final Configuration configuration = new Configuration();
try {
configuration.configure("hibernate.cfg.xml");
} catch (HibernateException e) {
throw new RuntimeException(e);
}
final SessionFactory sessionFactory = configuration.buildSessionFactory();
final Session session = sessionFactory.openSession();
session.beginTransaction();
final BoyFriend21 clinton = new BoyFriend21("Bill Clinton");
final GirlFriend21 monica = new GirlFriend21("monica lewinsky");
clinton.setGirlFriend(monica);
session.save(clinton);
session.getTransaction().commit();
session.close();
}
}
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import java.util.List;
public class Main578_Modify {
public static void main(String[] args) {
final Configuration configuration = new Configuration();
try {
configuration.configure("hibernate.cfg.xml");
} catch (HibernateException e) {
throw new RuntimeException(e);
}
final SessionFactory sessionFactory = configuration.buildSessionFactory();
final Session session1 = sessionFactory.openSession();
session1.beginTransaction();
GirlFriend21 monica = (GirlFriend21)session1.load(GirlFriend21.class,10); // Monica lewinsky record has id 10.
BoyFriend21 boyfriend = monica.getBoyFriends();
System.out.println(boyfriend.getName()); // It will print Clinton Name
monica.setBoyFriends(null); // It will not impact relationship
session1.getTransaction().commit();
session1.close();
final Session session2 = sessionFactory.openSession();
session2.beginTransaction();
BoyFriend21 clinton = (BoyFriend21)session2.load(BoyFriend21.class,10); // Bill clinton record
GirlFriend21 girlfriend = clinton.getGirlFriend();
System.out.println(girlfriend.getName()); // It will print Monica name.
//But if Clinton[Who owns the relationship as per "mappedby" rule can break this]
clinton.setGirlFriend(null);
// Now if Monica tries to check BoyFriend Details, she will find Clinton is no more her boyFriend
session2.getTransaction().commit();
session2.close();
final Session session3 = sessionFactory.openSession();
session1.beginTransaction();
monica = (GirlFriend21)session3.load(GirlFriend21.class,10); // Monica lewinsky record has id 10.
boyfriend = monica.getBoyFriends();
System.out.println(boyfriend.getName()); // Does not print Clinton Name
session3.getTransaction().commit();
session3.close();
}
}
回答by Ken Block
Simple rules of bidirectional relationships:
双向关系的简单规则:
1.For many-to-one bidirectional relationships, the many side is always the owning side of the relationship. Example: 1 Room has many Person (a Person belongs one Room only) -> owning side is Person
1.对于多对一的双向关系,多方始终是关系的拥有方。示例:1 个房间有很多人(一个人只属于一个房间)-> 拥有方是人
2.For one-to-one bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key.
2.对于一对一的双向关系,拥有方对应于包含相应外键的一方。
3.For many-to-many bidirectional relationships, either side may be the owning side.
3.对于多对多的双向关系,任何一方都可能是拥有方。
Hope can help you.
希望能帮到你。
回答by Vlad Mihalcea
Table relationships vs. entity relationships
表关系与实体关系
In a relational database system, there can be only three types of table relationships:
在关系数据库系统中,只能存在三种表关系:
- one-to-many (via a Foreign Key column)
- one-to-one (via a shared Primary Key)
- many-to-many (via a link table with two Foreign Keys referencing two separate parent tables)
- 一对多(通过外键列)
- 一对一(通过共享主键)
- 多对多(通过带有两个外键的链接表引用两个单独的父表)
So, a one-to-many
table relationshiplooks as follows:
因此,one-to-many
表关系如下所示:
Note that the relationship is based on the Foreign Key column (e.g., post_id
) in the child table.
请注意,该关系基于post_id
子表中的外键列(例如,)。
So, there is a single source of truth when it comes to managing a one-to-many
table relationship.
因此,在管理one-to-many
表关系时,只有一个事实来源。
Now, if you take a bidirectional entity relationship that maps on the one-to-many
table relationship we saw previously:
现在,如果您采用映射到one-to-many
我们之前看到的表关系的双向实体关系:
If you take a look at the diagram above, you can see that there are two ways to manage this relationship.
如果你看一下上面的图表,你会发现有两种方法可以管理这种关系。
In the Post
entity, you have the comments
collection:
在Post
实体中,您有以下comments
集合:
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
And, in the PostComment
, the post
association is mapped as follows:
并且,在 中PostComment
,post
关联映射如下:
@ManyToOne(
fetch = FetchType.LAZY
)
@JoinColumn(name = "post_id")
private Post post;
So, you have two sides that can change the entity association:
因此,您有两个方面可以更改实体关联:
- By adding an entry in the
comments
child collection, a newpost_comment
row should be associated with the parentpost
entity via itspost_id
column. - By setting the
post
property of thePostComment
entity, thepost_id
column should be updated as well.
- 通过在子集合中添加条目
comments
,新post_comment
行应post
通过其post_id
列与父实体相关联。 - 通过设置实体的
post
属性PostComment
,post_id
列也应该更新。
Because there are two ways to represent the Foreign Key column, you must define which is the source of truth when it comes to translating the association state change into its equivalent Foreign Key column value modification.
因为有两种方式来表示外键列,所以在将关联状态更改转换为其等效的外键列值修改时,您必须定义哪一种是真实来源。
MappedBy (a.k.a the inverse side)
MappedBy(又名反面)
The mappedBy
attribute tells that the @ManyToOne
side is in charge of managing the Foreign Key column, and the collection is used only to fetch the child entities and to cascade parent entity state changes to children (e.g., removing the parent should also remove the child entities).
该mappedBy
属性告诉@ManyToOne
一侧负责管理外键列,集合仅用于获取子实体并将父实体状态更改级联到子实体(例如,移除父实体也应移除子实体)。
It's called the inverse sidebecause it references the child entity property that manages this table relationship.
之所以称为反向端,是因为它引用了管理此表关系的子实体属性。
Synchronize both sides of a bidirectional association
同步双向关联的双方
Now, even if you defined the mappedBy
attribute and the child-side @ManyToOne
association manages the Foreign Key column, you still need to synchronize both sides of the bidirectional association.
现在,即使您定义了mappedBy
属性并且子端@ManyToOne
关联管理了外键列,您仍然需要同步双向关联的双方。
The best way to do that is to add these two utility methods:
最好的方法是添加这两个实用方法:
public void addComment(PostComment comment) {
comments.add(comment);
comment.setPost(this);
}
public void removeComment(PostComment comment) {
comments.remove(comment);
comment.setPost(null);
}
The addComment
and removeComment
methods ensure that both sides are synchronized. So, if we add a child entity, the child entity needs to point to the parent and the parent entity should have the child contained in the child collection.
该addComment
和removeComment
方法确保双方是同步的。所以,如果我们添加一个子实体,子实体需要指向父实体,父实体应该将子实体包含在子集合中。
For more details about the best way to synchronize all bidirectional entity association types, check out this article.
有关同步所有双向实体关联类型的最佳方法的更多详细信息,请查看本文。