简体   繁体   English

双向 JPA OneToMany/ManyToOne 关联中的“关联的反面”是什么?

[英]What is “the inverse side of the association” in a bidirectional JPA OneToMany/ManyToOne association?

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.在我看来, Customer实体是关联的所有者。 However, in the explanation for the mappedBy attribute in the same document, it is written that:但是,在同一个文档中对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:所以我的问题基本上是:

  1. 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?我们如何指定多方为所有者?

  2. 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?我们如何将多边指定为逆?

To understand this, you must take a step back.要理解这一点,您必须退后一步。 In OO, the customer owns the orders (orders are a list in the customer object).在 OO 中,客户拥有订单(订单是客户对象中的一个列表)。 There can't be an order without a customer.没有客户就不可能有订单。 So the customer seems to be the owner of the orders.所以客户似乎是订单的所有者。

But in the SQL world, one item will actually contain a pointer to the other.但在 SQL 世界中,一项实际上将包含指向另一项的指针。 Since there is 1 customer for N orders, each order contains a foreign key to the customer it belongs to.由于 N 个订单有 1 个客户,因此每个订单都包含其所属客户的外键。 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.这与面向对象/模型世界完全相反。

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.反面是对象的 OO“所有者”,在这种情况下是客户。 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 ).客户在表中没有用于存储订单的列,因此您必须告诉它可以在订单表中保存此数据的位置(通过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.这就是为什么我很少推荐这种方法。

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").您可以将任何一方指定为所有者,但是,如果您将 One 指定为所有者,则关系将不是双向的(反面也称为“多”方将不知道其“所有者”)。 This can be desirable for encapsulation/loose coupling:这对于封装/松散耦合是可取的:

// "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.唯一的双向映射解决方案是让“多”方拥有指向“一”的指针,并使用@OneToMany“mappedBy”属性。 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)).如果没有“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;
}

数据库中具有外键表的实体是拥有实体,指向的另一个表是逆实体。

Simple rules of bidirectional relationships:双向关系的简单规则:

1.For many-to-one bidirectional relationships, the many side is always the owning side of the relationship. 1.对于多对一的双向关系,多方始终是关系的拥有方。 Example: 1 Room has many Person (a Person belongs one Room only) -> owning side is Person示例: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.希望能帮到你。

For two Entity Classes Customer and Order , hibernate will create two tables.对于两个实体类 Customer 和 Order ,hibernate 将创建两个表。

Possible Cases:可能的情况:

  1. mappedBy is not used in Customer.java and Order.java Class then->在 Customer.java 和 Order.java 类中不使用 mappingBy then->

    At customer side a new table will be created[name = CUSTOMER_ORDER] which will keep mapping of CUSTOMER_ID and ORDER_ID.在客户端,将创建一个新表 [name = CUSTOMER_ORDER],它将保持 CUSTOMER_ID 和 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.在订单端,需要一个额外的列来保存相应的 Customer_ID 记录映射。

  2. mappedBy is used in Customer.java [As given in problem statement] Now additional table[CUSTOMER_ORDER] is not created.在Customer.java 中使用了mappedBy [如问题陈述中给出的] 现在没有创建附加表[CUSTOMER_ORDER]。 Only one column in Order Table订单表中只有一列

  3. 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.在 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.在下面的示例中 BoyFriend 类是关系的所有者。 even if Girlfriend wants to break-up , she can't.就算女朋友想分手,她也做不到。

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();
    }
}

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 relationship looks as follows:因此, one-to-many表关系如下所示:

一对多表关系

Note that the relationship is based on the Foreign Key column (eg, 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我们之前看到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:并且,在PostCommentpost关联映射如下:

@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 new post_comment row should be associated with the parent post entity via its post_id column.通过在comments post_comment添加一个条目,一个新的post_comment行应该通过它的post_id列与父post实体相关联。
  • By setting the post property of the PostComment entity, the post_id column should be updated as well.通过设置PostComment实体的post属性,也应该更新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 (aka 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 (eg, removing the parent should also remove the child entities).mappedBy属性告知@ManyToOne方负责管理外键列,并收集只用于获取子实体和级联父实体状态更改为儿童(例如,移除家长也应该删除子实体)。

It's called the inverse side because 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. addCommentremoveComment方法确保双方同步。 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.所以,如果我们添加一个子实体,子实体需要指向父实体,父实体应该将子实体包含在子集合中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM