添加到@ManyToMany的拥有方集合时,避免选择所有行的最佳做法

时间:2014-07-28 22:23:47

标签: java sql hibernate jpa many-to-many

当添加到代表@ManyToMany关联的拥有方的集合时,我的JPA实现(Hibernate)将首先从关联中选择所有行,以确定实体已经存在于集合中。

我理解这背后的机制,但是在处理大型连接表时,这不是很有效。当我知道需要插入条目时,避免加载连接表的所有元素的最佳做法是什么?

我将举例说明一个典型的用户/角色场景,为了简洁起见,遗漏了getters / setters / initializers:

@Entity
public class User {
    @Id
    private Long id;

    @ManyToMany
    private Set<Role> roles;
}

@Entity
public class Role {
    @Id
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "roles")
    private Set<User> users;
}

我让User成为拥有者,以便JPA跟踪对user.roles的更改。

以下代码导致问题:

User user = em.find(User.class, 1L);
Role role = em.find(Role.class, 1L);

// This line causes the issue
user.getRoles().add(role);

em.persist(user);

当我添加到user.roles时,执行以下SELECT:

select
     roles0_.users_id as users_id1_20_0_,
     roles0_.roles_id as roles_id2_21_0_,
     role1_.id as id1_17_1_,
     role1_.name as name2_17_1_ 
from User_Role roles0_ 
inner join Role role1_ on roles0_.roles_id=role1_.id 
where roles0_.users_id=?

这适用于小型集合,但对于较大的集合则有问题。

我可以想到以下解决方案,我想知道应该选择哪一个,或者是否有更好的方法可以做到这一点?

1。执行本机查询:

INSERT INTO User_Role (users_id, roles_id) VALUES (1, 1)

2。为连接表创建实体:

@Entity
IdClass(UserRole.PK)
public class UserRole {
    @Id
    private User user;
    @Id
    private Role role;

    public static class PK {
        private User user;
        private Role role;
    }
}

然后我可以跑:

User user = em.find(User.class, 1L);
Role role = em.find(Role.class, 1L);
UserRole userRole = new UserRole(user, role);
em.persist(userRole);

我倾向于使用插入的原生查询,但我希望得到一些关于最多&#39; JPA&#39;这样做的方式。

1 个答案:

答案 0 :(得分:1)

使用hibernate书的java持久性说明(第298页),许多人通常最好使用关联类(有点像你在第二个解决方案中已经使用UserRole),然后将两个一对一映射到 - 任何一方的多少关系 - 即每个用户都有很多UserRoles,每个角色都有很多UserRoles。这是最“JPA”的做事方式,我认为会得到你想要的表现。

现在有了细微之处:

  1. 关联类应该有一个基于用户和角色的ID的复合键,而不是它自己的PK。本书给出了一个在关联类中创建一个@Embeddable静态内部类的示例,该内部类包含两个主要类的ID(在您的情况下,用户和角色)。这些id到关联表中的列的映射是在这个内部类中完成的。
  2. 在关联类的构造函数中,您传递特定角色和用户,您将填充内部类,然后将“this”(即您正在创建的关联实例)添加到用户的集合中和角色传入(例如role.getUserRoles()。add(this)。
  3. 删除关联时,必须从用户和角色中删除关联。即在角色方面,你会这样做:role.getUserRoles()。remove(userRole)然后你会在用户端做同样的事情,然后你会删除关联:session.delete(userRole)。
  4. 如果你按照这些步骤操作,hibernate会知道正在发生的一切,你的缓存应该是好的。您还可以使用级联启用传递持久性。

    编辑:正如所指出的,这实际上并没有消除试图避免的查询。经过进一步的反思,我没有一个可以消除查询的答案,但我可以指出为什么JPA会以你看到的方式表现。在原始设置中,每个项目都有其他内容的 Set 。由于集合是唯一的,并且JPA提供程序遵循集合语义,因此它们必须确保关系是唯一的。因此,如果添加关系,则必须进行查询以确保关系尚不存在。它可以只查询您要添加的关系,或者可以查询整个集合,然后检查内部。如果要向集合添加多个内容,后者是更好的方法,这就是他们优化的内容。如果您尝试添加重复关系,JPA提供程序永远不会做的一件事是依赖于基础数据库约束 - JPA提供程序更喜欢在Java层处理Java约束

    Hibernate还支持一个包选项,它允许重复,因此可以避免检查......但是,你的数据库中会有重复的关系。