3

我很难创建关系的最佳 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 的 Owner 部分的 Friend 中,但这给了我运行时错误。

任何帮助表示赞赏。

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

4

3 回答 3

3

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

一旦你有了它,你需要修复你的映射:

  1. Friend 和 Person 之间的关联不是 OneToOne,而是 ManyToOne,因为几个人可以成为另一个人的朋友。
  2. mappedBy应该在另一个实体中保存字段的名称,它代表关联的另一端。在您的情况下,它是“key.owner”。如果您停止使用复合键,它将只是“所有者”。

最后,在双向 OneToMany 关联中,所有者始终是多方(在这种情况下,即朋友)。因此,您必须初始化friend.owner 字段以保持关联。但是,如果您封装朋友列表而不是让它从外部访问,那将很简单。而不是做

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

简单地做

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

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

friendToAdd.setOwner(this);
于 2013-01-31T20:06:14.090 回答
2

好吧,所以如果你想要一个解决这个问题的方法,它可以在纯 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;

      ...
   }
} 
于 2013-02-01T16:55:24.127 回答
1

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

WhilePerson显然是一个实体,friend是一个关系。不仅如此,friend还是双向关系。(A 是 B 的朋友 => B 是 A 的朋友)

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

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

}

这是 untestet,但应该给你一个像这样的表:

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 的朋友中,您将不会在 B 的朋友列表中找到 A。您必须注意这一点!

于 2013-01-31T20:32:12.420 回答