【问题标题】:how to stop Hibernate/JPA from deleting join table records when I update entity in ManyToMany relationship?当我更新多对多关系中的实体时,如何阻止 Hibernate/JPA 删除连接表记录?
【发布时间】:2020-09-01 15:37:53
【问题描述】:

我想更新实体而不更新/编辑子实体。 这是有问题的实体:

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;
    
    //other fields omitted for brevity
    
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "project_user",
    joinColumns = @JoinColumn(name="user_id", updatable = false),
    inverseJoinColumns = @JoinColumn(name="project_id",updatable = false))
    @JsonIgnore
    private Set<Project> projects;
    
    //getters and setters omitted for brevity

    @Override
    public int hashCode() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        if (id != other.id)
            return false;
        return true;
    }
    
}

这里是项目实体:

@Entity
@Table(name = "project")
public class Project {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;
    
    //other fields omitted for brevity
    
    @ManyToMany
    @JoinTable(name = "project_user",
    joinColumns = @JoinColumn(name="project_id"),
    inverseJoinColumns = @JoinColumn(name="user_id"))
    @JsonIgnoreProperties(value = {"projects", "connected", "notifications"})
    private Set<User> members;

    @Override
    public int hashCode() {
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Project other = (Project) obj;
        if (id != other.getId())
            return false;
        return true;
    }
    
}

这是合并过程:

@Override
    public void update(User user) {
        //user is created from a dto as a new object (that has its id set manually)
        entityManager.merge(user);
    }

问题是从连接表中删除休眠日志。我怎样才能阻止它?

休眠日志

Hibernate: //login query
    select
        distinct authuser0_.id as id1_7_,
        authuser0_.enabled as enabled8_7_,
        authuser0_.password as password5_7_,
        authuser0_.username as username6_7_,
        roles1_.username as username1_0_0__,
        roles1_.authority as authorit2_0_0__ 
    from
        users authuser0_ 
    inner join
        authorities roles1_ 
            on authuser0_.username=roles1_.username 
    where
        authuser0_.username=?
Hibernate: //entityManager.merge() calls this to merge it
    select 
        user0_.id as id1_7_0_,
        user0_.email as email2_7_0_,
        user0_.first_name as first_na3_7_0_,
        user0_.image as image7_7_0_,
        user0_.last_name as last_nam4_7_0_,
        user0_.password as password5_7_0_,
        user0_.username as username6_7_0_ 
    from
        users user0_ 
    where
        user0_.id=?
Hibernate: //the actual update statement
    update 
        users 
    set
        email=?,
        first_name=?,
        image=?,
        last_name=?,
        password=?,
        username=? 
    where
        id=?
Hibernate: // not wanted, need to stop this query
    delete 
    from
        project_user 
    where
        user_id=?

我在网上读到它与equals和hashcode有关。但是,我似乎无法让它工作。

我知道两个都不是最佳选择:

  1. 编写本机查询而不是合并方法。问题:不可扩展,每次更改都需要手动编辑
  2. 为表创建一个新实体(因此该表将有 2 个映射到它的实体),而不附加到其他表。问题:重复的代码和实体

提前致谢

【问题讨论】:

    标签: mysql spring spring-boot hibernate jpa


    【解决方案1】:

    嗯,我知道你已经接受了答案,但是......

    那里发生的事情比你想象的要多。

    Hibernate: delete from project_user where user_id=? 的原因是后面有插入。你没看到他们吗?如果为该用户清空了关系表但后来没有插入,则关系可能没有更新?只能复制删除,但您应该测试您的代码并验证该用户没有项目。这可能意味着您要么没有更新合并用户中的项目字段。

    所以,运行一个基本的 JPA 示例,让我们通过创建一个起点来解决您的问题:

        em.getTransaction().begin();
        Project project = new Project();
        em.persist(project);
        User user = new User();
        Set<Project> projects = new HashSet<>();
        projects.add(project);
        user.setProjects(projects);
        em.persist(user);
        em.getTransaction().commit();
    

    然后,清除 entityManager 上下文:

        em.clear();
    

    然后重现你的问题:

        em.getTransaction().begin();
        Project p2 = new Project();
        em.persist(p2);
        projects.add(p2);
        User un = em.find(User.class, 1);
        un.setProjects(projects);
        em.merge(un);
        em.getTransaction().commit();
    

    第二部分给了我日志:

    Hibernate: insert into project (id) values (default)
    Hibernate: select user0_.id as id1_2_0_ from users user0_ where user0_.id=?
    Hibernate: select project0_.id as id1_0_0_ from project project0_ where project0_.id=?
    Hibernate: delete from project_user where user_id=?
    Hibernate: insert into project_user (user_id, project_id) values (?, ?)
    Hibernate: insert into project_user (user_id, project_id) values (?, ?)
    

    现在让我们通过更改代码并再次运行它来删除有问题的delete 语句:

        Project p2 = new Project();
        em.persist(p2);
        User un = em.find(User.class, 1);
        projects = un.getProjects();
        projects.add(p2);
        em.merge(un);
        em.getTransaction().commit();
    

    这给了我日志:

    Hibernate: insert into project (id) values (default)
    Hibernate: select user0_.id as id1_2_0_ from users user0_ where user0_.id=?
    Hibernate: select projects0_.user_id as user_id1_1_0_, projects0_.project_id as project_2_1_0_, project1_.id as id1_0_1_ from project_user projects0_ inner join project project1_ on projects0_.project_id=project1_.id where projects0_.user_id=?
    Hibernate: insert into project_user (user_id, project_id) values (?, ?)
    

    没有删除语句。仔细查看第一个“第二部分”,注意我将新项目添加到不再属于 JPA 持久性上下文的原始项目集中。在第二个示例中,我专门从作为 JPA 持久性上下文的一部分的 user 对象中检索了一组项目。现在,当我更新项目集并合并用户时,JPA 不必重新开始用户/项目关系。

    顺便说一句:就mappedBy 逻辑而言,接受的答案是准确的,但此代码是使用您的原始示例完成的。只要 JPA 知道您在做什么,它就会正确处理它。当您在 JPA 之外执行操作时,JPA 将“踢”并重新创建。

    【讨论】:

    • 我明白了。删除后我没有看到任何重新插入(除非我在试验期间做错了什么)。谢谢你给我解释
    • 只能复制删除,但是您应该测试您的代码(如果您仍然感兴趣的话)并验证该用户没有项目。这可能意味着您没有更新合并用户中的项目字段或其他内容。感谢您的支持。
    • 不用感谢,您给出了一个全面的答案,有助于我理解未来的努力。可能有两种情况:要么我得到的只是从我的混乱中删除语句(最不可能,因为我尝试了多件事)或者第一个只是删除而其他没有重新插入,因为什么都没有(很可能,因为我没有在每次测试后重新填充数据,我只是查看了执行查询的日志)。稍后测试它可能是值得的。无论如何,谢谢你:)
    【解决方案2】:

    为什么两个不同的关联(User.projectsProject.users)映射到同一个连接表?

    如果你想要一个双向关联,只有一方应该是拥有方,另一方应该映射为mappedBy。顺便说一句,将mappedBy 用于User.projects 端将解决您的问题。

    【讨论】:

    • 它有效,但我不明白一件事。多对多关系不是非拥有关系吗? (没有所有者,因为两者都连接到连接表)。我将如何选择谁是所有者? (或者我只是选择其中任何一个?)
    • Hibernate 会忽略对反面的更改。您基本上需要根据用例做出决定,即您需要问自己:'大多数时候,我会在Users 中添加新的Projects 还是相反?'
    • 我通常会向用户添加项目。因此,在这种情况下,拥有方将是项目实体。对吗?
    • 好吧,如果你的意思是通过在数据库中找到User,然后找到Project,然后调用user.getProjects().add(project),然后User 将有成为它工作的拥有者。但是,鉴于在这种情况下,project.getUsers().add(user) 同样容易做到,我不会考虑那么多
    • 唯一要记住的是,如果你在反面添加一个元素,它将不会在DB中被反映
    猜你喜欢
    • 1970-01-01
    • 2011-03-31
    • 1970-01-01
    • 1970-01-01
    • 2012-11-09
    • 2020-01-23
    • 2011-08-04
    • 2021-07-12
    • 2023-03-05
    相关资源
    最近更新 更多