Java 当添加到@ManyToMany的拥有方集合时,最好避免选择所有行

Java 当添加到@ManyToMany的拥有方集合时,最好避免选择所有行,java,sql,hibernate,jpa,many-to-many,Java,Sql,Hibernate,Jpa,Many To Many,当添加到表示@ManyToMany关联的拥有方的集合时,我的JPA实现(Hibernate)将首先从该关联中选择所有行,以确定该实体是否已存在于集合中 我理解这背后的机制,但在处理大型联接表时,这不是很有效。当我知道需要插入一个条目时,避免加载联接表的所有元素的最佳做法是什么 我将以一个经典的用户/角色场景为例,为简洁起见,省略了getter/setter/initializer: @Entity public class User { @Id private Long id;

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

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

我将以一个经典的用户/角色场景为例,为简洁起见,省略了getter/setter/initializer:

@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.roles时,将执行以下选择:

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.执行本机查询: 2.为联接表创建实体: 然后我可以运行:

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

我倾向于使用本机查询来进行插入,但我很想得到一些反馈,了解最“JPA”的方法是什么。

java持久化与hibernate的书(第298页)说,多对多通常最好使用关联类(有点像第二个解决方案中已经使用的,使用UserRole)然后映射任意一方的两个一对多关系——即每个用户有多个userrole,每个角色有多个userrole。这是最“JPA”的做事方式,我认为会得到你想要的性能

现在让我们来看看其中的微妙之处:

  • 关联类应该有一个基于用户和角色ID的复合密钥,而不是它自己的PK。这本书给出了一个在关联类中创建@Embeddeble静态内部类的示例,该类保存两个主要类(在您的例子中为user和role)的ID。这些ID到关联表中的列的映射在这个内部类中完成
  • 在向其传递特定角色和用户的关联类的构造函数中,您将填充内部类,然后将“this”(即您正在创建的关联实例)添加到传递的用户和角色集(例如role.getUserRoles().add(this))
  • 删除关联时,必须同时从用户和角色中删除关联。例如,在角色端,可以执行以下操作:role.getUserRoles().remove(userRole),然后在用户端执行相同操作,然后删除关联:session.delete(userRole)
  • 如果您遵循这些步骤,hibernate将了解所有正在发生的事情,并且您的缓存应该是良好的。您还可以通过级联启用可传递持久性

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


    Hibernate还支持bag选项,该选项允许重复,因此可能会避免检查…但是,您的数据库中会有重复的关系。

    您的两种解决方案至少有一个缺点。只需在二级缓存或当前持久性上下文中查看它。您必须非常小心,不要出错我将跟踪这篇文章,这是一个非常有趣的问题。谢谢@zxcf-我没有考虑持久性上下文/二级缓存,但显然这需要解决。考虑到这一点,绝对需要一个更好的解决方案来避免错误和不必要的管道,如缓存逐出等。谢谢@DavidInTx-当我第一次访问时读了你的帖子,我觉得“就是这样!”,但后来我开始思考——我需要执行role.getUserRoles().remove(userRole)等操作,这难道不会导致Hibernate加载所有关联,就像我最初的困境一样吗?
    INSERT INTO User_Role (users_id, roles_id) VALUES (1, 1)
    
    @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);