与JPA的朋友关系

时间:2013-01-31 19:39:20

标签: sql hibernate jpa

我很难创建关系的最佳JPA表示。似乎有很多方法可以做到这一点,我不确定哪个是最优的。没有JPA,我可能将结构表示为PERSON表和FRIEND表。假设PERSON表只有一个ID和NAME。 FRIEND表有一个OWNER_ID,一个PERSON_ID和一个可设置的布尔IS_ACTIVE字段。 OWNER_ID和PERSON_ID都引用PERSON表。一个人可以有很多朋友,所以唯一的主键是OWNER_ID和PERSON_ID。在我的代码中,我希望PersonEntity控制关系。 Java中的操作可能如下所示:

person1.getFriends().add( new Friend( person2, isActive ) );
Friend friend = person1.findFriend( person2ID );

这样的事情。请注意,我会喜欢 JPA来隐式分配朋友关系的OWNER。在上面的代码中,我没有在Friend构造函数中传递它,我只传递复合键的PERSON部分。

这是我对此的第一次猜测:

@Entity
public class Person {
   @Id
   @GeneratedValue
   private Long id;

    @OneToMany( mappedBy="key.owner", cascade=CascadeType.ALL, orphanRemoval = true)
    private Set<Friend> friends = new HashSet<>();

    // getter and setter for Friends
    ...
}

@Entity
public class Friend {
   @EmbeddedId
   private Key key = new Key();

   private boolean isActive;

   ...

   @Embeddable
   public static class Key implements Serializable {
      @ManyToOne
      private Person owner;

      @OneToOne
      private Person person;

      ...
   }
}

但是当我使用这段代码时,我得到了堆栈溢出错误。我假设它被OneToOne关系搞糊涂了。

在JPA 2.0中建模这种关系的最佳方法是什么?我想最简单的方法是在Friend中引入生成的长键。但是,这似乎会使查询更加复杂,因为我将引入第三个映射表,基本上在SQL中映射关系两次。另一种方法是使其成为单向OneToMany关系,甚至不在Friend中明确指定OWNER。这将为映射引入另一个表,并使查询有点复杂。使用ElementCollection而不是OneToMany似乎是直截了当的,但后来我的理解是我无法将Friend作为一个实体来管理?

如果您需要更多细节或要求,请告诉我。顺便说一句,我尝试将一个MapsId放在朋友中为PKEY的所有者部分,但这给了我运行时错误。

感谢任何帮助。

注意:即使我向Friend添加了生成的密钥,我也希望在Friend实体中强制{owner,person}组合的唯一性。

3 个答案:

答案 0 :(得分:3)

我确实会在Friend实体中添加一个自动生成的ID。它会使事情变得更简单(没有包含关联的复合键)。我不知道如何添加这样的ID(以及因此在Friend表中添加这样的附加列)会使任何查询更复杂,或者引入新表。它没有赢。您只需要一个额外的列,作为好友表的主键。

完成后,您需要修复映射:

  1. 朋友和人之间的联系不是OneToOne,而是ManyToOne,因为有几个人可以成为另一个人的朋友。
  2. mappedBy应该在另一个实体中保存该字段的名称,该实体代表该关联的另一侧。在你的情况下,它就是&#34; key.owner&#34;。如果您停止使用复合键,它将只是&#34;所有者&#34;。
  3. 最后,在双向OneToMany协会中,所有者总是多方(即朋友,在这种情况下)。因此,您必须初始化friend.owner字段才能保持关联。但是如果你封装了朋友列表而不是让它可以从oustide访问,那就很简单了。而不是做

    person1.getFriends().add( new Friend( person2, isActive ) );
    

    简单地做

    person1.addFriend( new Friend( person2, isActive ) );
    

    并确保addFriend()方法包含以下指令:

    friendToAdd.setOwner(this);
    

答案 1 :(得分:1)

好吧,如果你想要一个解决这个问题的解决方案来创建一个人们希望在纯SQL中使用的表 - 这不是人工代理键​​和应用程序数据作为复合键 - 你绝对可以做到。但是,使用JPA会使它比您想要的更加混乱。

感谢@JB Niznet的出色表现,你也可以通过引入一个替代密钥来做到这一点。下面的解决方案只是消除了这种需求,据我所知,没有任何缺点。

@Entity
public class Person {
   @Id
   @GeneratedValue
   private Long id;

   @OneToMany( mappedBy="owner", cascade=CascadeType.ALL, orphanRemoval = true)
   private Set<Friend> friends = new HashSet<>();

   // getter and setter for Friends
   ...
}

@Entity
public class Friend {
   @EmbeddedId
   private Key key = new Key();

   @ManyToOne
   @Maps("ownerId")
   private Person owner;

   @ManyToOne
   @MapsId("personId")
   private Person person;

   private boolean isActive;

   ...

   @Embeddable
   public static class Key implements Serializable {
      private Long ownerId;
      private Long personId;

      ...
   }
} 

答案 2 :(得分:0)

您必须考虑设计:实体将是类并具有表。关系只会有表格,因为关系只是连接2个实体! (除了关系具有属性,例如在这种情况下的“朋友评级”等。)

虽然Person显然是实体,但friend是一种关系。更重要的是,friend是一种双向关系。 (A是B => B的朋友是A的朋友)

因此,您只需要通过人员列表扩展您的Person对象 - 称为朋友:

@Entity
public class Person {
   @Id
   @GeneratedValue
  private Long id;

  @OneToMany
  @JoinTable(name="friends")
  @JoinColumn(name="person_A_id", referencedColumnName="id"), @JoinColumn(name="person_B_id", referencedColumnName="id")) 
  private Set<Person> friends = new HashSet<>();

}

这是不可测试的,但是应该给你一个这样的表:

person_a_id | person_b_id
1             2
1             6
2             7

然后你可以简单地使用你的实体,比如

Person A = new Person();
Person B = new Person();
A.friends.add(B);

然而,请记住,JPA将无法将此双向线程化:因此,如果您将B添加到A'S的朋友,您将不会在B的朋友列表中找到A.您必须注意这一点!