java - 添加到 @ManyToMany 的拥有侧集合时避免选择所有行的最佳实践

标签 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。执行 native 查询:

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

我倾向于对插入使用 native 查询,但我很想获得一些反馈,以了解执行此操作的最“JPA”方式。

最佳答案

java persistence with hibernate 一书说(第 298 页)多对多通常最好使用关联类(有点像第二个解决方案中已有的 UserRole)然后映射两个一对多- 双方的许多关系 - 即每个用户有很多 UserRoles,每个角色有很多 UserRoles。这是最“JPA”的做事方式,我认为会获得您想要的性能。

现在是微妙之处:

  1. 关联类应该有一个基于用户和角色 ID 的组合键,而不是它自己的 PK。这本书给出了一个在关联类内部创建一个 @Embeddable 静态内部类的示例,该内部类包含两个主要类(在您的情况下为用户和角色)的 id。这些ids到关联表中列的映射是在这个内部类中完成的。
  2. 在您向其传递特定角色和用户的关联类的构造函数中,您将填充内部类,然后将“this”(即您正在创建的关联实例)添加到两个用户的集合中和正在传递的角色(例如 role.getUserRoles().add(this)。
  3. 删除关联时,必须同时从用户和角色中删除该关联。即在角色方面,你会这样做:role.getUserRoles().remove(userRole) 然后你会在用户方面做同样的事情,然后你会删除关联:session.delete(userRole)。<

如果您按照这些步骤操作,hibernate 将知道正在发生的一切,并且您的缓存应该是好的。您还可以通过级联启用传递持久性。

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

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

关于java - 添加到 @ManyToMany 的拥有侧集合时避免选择所有行的最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25005315/

相关文章:

java - 在 Android 应用程序中使用 Maven 项目

java - Spring/JPA/Hibernate 可以使用简单的 JDBC 兼容驱动程序吗?

java - 从 BaseAdapter 内的 Activity 调用函数

php - 如何通过在 php 中使用 javascript 选择从 SQL 填充的下拉列表来将 SQL 中的值检索到文本区域中

java - Hibernate - 在为对象设置属性时避免冗余查询

mysql - hibernate 标准对一组元素放置包含/不包含限制

java - LibGDX 调整窗口大小后如何更新 Actor 位置?

sql - 日期类型的查询 substr 的索引是什么?

mysql - 海量sql删除查询

java - MySQL 触发器通过直接插入工作,但在通过 Hibernate 提交时不工作