首页 > 解决方案 > Hibernate 自动删除表中的行而没有明确告知?(@ManyToMany 协会,Spring JPA)

问题描述

我正在开发一个基于微服务的 Spring JPA 项目,其中我有两个实体,UserEntity 和 RegionEntity,通过多对多关系关联。在两个实体的(简化)代码下方:

@EqualsAndHashCode(of = {"id"})
@Data
@Entity
@Table(name = "users")
@Audited
public class UserEntity implements Serializable {

    @Id
    @Column(name = "userId", columnDefinition = "int", unique = true, nullable = false)
    private Long id;

    @NotAudited
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "users_regions",
            joinColumns        = @JoinColumn(name = "userId", columnDefinition = "int", nullable = false, updatable = false),
            inverseJoinColumns = @JoinColumn(name = "regionId", columnDefinition = "varchar(50)", nullable = false, updatable = false))
    private Set<RegionEntity> regions = new HashSet<>();
}

@EqualsAndHashCode(exclude = {"users"})
@Entity
@Table(name = "regions")
@Data
@Audited
public class RegionEntity {
    @Id
    @Column(name = "name", columnDefinition = "varchar(50)", unique = true, nullable = false)
    private String name;

    @ManyToMany(mappedBy = "regions", fetch = FetchType.EAGER)
    private Set<UserEntity> users = new HashSet<>();
}

现在,我有一个用于持久化第三个实体 CallEntity 实例的路由,它利用了前面的两个实体。知道 CallEntity 还与 RegionEntity 具有多对多关联以及与 user 具有多对一关联可能很有用。简而言之,这是路由调用的方法。

public CallDTO myMethod(String userName, /* ... more stuff */) {
    UserProjection userProj = userConsumer.findByUserName(userName, UserBasicProjection.class); // from another repository, retrieve a user projection
    UserEntity user = userRepository.findById(user.getId()).orElse(null); // use that projection to initialise the user
    Set<RegionEntity> userRegions = user != null ? user.getRegions() : null;
    CallEntity newCall = new CallEntity();
    / * some actions, mainly setting values for newCall using userRegions and user... */

    CallEntity call = callRepository.save(newCall);

    return callMapper.toDTO(call);
}

问题是 Hibernate 在保存 CallEntity 对象时会自动删除关联表中与用户区域对相关的行。例如,如果用户的 id 为 1234,并且关联了区域 A、B 和 C,

user | region
-------------
1234 |   A
-------------
1234 |   B 
-------------
1234 |   C  
-------------

那么这行代码之后的表中的所有三行都将被删除:

CallEntity call = callRepository.save(newCall);

被执行。

这是当前使用的 Hibernate 版本:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-jpamodelgen</artifactId>
    <version>5.3.12.Final</version>
</dependency>

这是执行后的控制台打印输出:

Hibernate: 
    /* get current state package-name.UserEntity */ select
        userent_.userId 
    from
        user userent_ 
    where
        userent_.userId=?
Hibernate: 
    /* get current state package-name.RegionEntity */ select
        regione_.name
    from
        region regione_ 
    where
        regione_.name=?
Hibernate: 
    /* insert package-name.CallEntity
        */ insert 
        into
            call
            (id, userid, regionid) 
        values
            (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    select
        last_insert_id()
Hibernate: 
    /* delete collection package-name.UserEntity.regions */ delete 
        from
            users_regions 
        where
            userId=?
Hibernate: 
    /* insert collection
        row package-name.CallEntity.userRegions */ insert 
        into
            call_region
            (id, name) 
        values
            (?, ?)

我发现如果我改变关系的方向(在这种情况下,拥有实体是 RegionEntity)问题就会消失。然而,这绝不是一个可行的解决方案,因为它会影响项目的其他部分。另外,我发现之前在这个网站上问过一个类似的问题(ManyToMany assoicate delete join table entry),但不幸的是答案并不令人满意。我尝试添加和使用便捷方法来正确建立关联(正如链接问题的答案所暗示的那样),但这不起作用。

任何帮助将不胜感激。

谢谢你。

标签: javaspring-boothibernatejpamany-to-many

解决方案


最后,通过将一组区域包装在一个副本中解决了这个问题。

也就是说,通过替换这一行:

设置 userRegions = user != null ?user.getRegions() : null;

和:

设置 userRegions = userxpc != null ?Set.copyOf(userxpc.getRegions()) : null;


推荐阅读