Hibernate 两次将数据插入到 Join Table 中,导致 Spring Boot 项目中出现“ORA-00001:违反唯一约束”

Posted

技术标签:

【中文标题】Hibernate 两次将数据插入到 Join Table 中,导致 Spring Boot 项目中出现“ORA-00001:违反唯一约束”【英文标题】:Hibernate inserts data into Join Table twice causing "ORA-00001: unique constraint violated" in Spring Boot project 【发布时间】:2020-04-20 03:02:57 【问题描述】:

我有两个用@ManyToMany 注释映射的实体

第一

皮莱尼亚卡:


 @Data
 @Entity
 @Table(name = "pielegniarka")
 public class Pielegniarka 

    @Id
    @SequenceGenerator(name = "seq2", sequenceName = "pielegniarka_id_pielegniarki", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq2")
    @Column(name = "id_pielegniarki", nullable = false, unique = true)
    private int id_pielegniarki;

    @Column(name = "imie")
    private String imie;

    @Column(name = "nazwisko")
    private String nazwisko;

    @Column(name = "placa")
    private int placa;


    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.DETACH,CascadeType.MERGE,
            CascadeType.PERSIST,CascadeType.REFRESH)
    @JoinTable(
            name = "pielegniarki_sale",
            joinColumns = @JoinColumn(name = "id_pielegniarki"),
            inverseJoinColumns = @JoinColumn(name = "nr_sali")
    )
    private List<Sala> sale;


    public Pielegniarka() 
    

    public Pielegniarka(String imie, String nazwisko, int placa, List<Sala> sale) 
        this.imie = imie;
        this.nazwisko = nazwisko;
        this.placa = placa;
        this.sale = sale;
    

    public void addSala(Sala sala)
        if(sale == null)
            sale = new ArrayList<>();
        
        sale.add(sala);
    

    public void removeSala(Sala sala)
        sale.remove(sala);
        sala.getPielegniarki().remove(this);
    

第二次

萨拉:

@Data
@Entity
@Table
public class Sala 

    @Id
    @SequenceGenerator(name = "seq3", sequenceName = "sala_nr_sali_seq", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq3")
    @Column(name = "nr_sali", nullable = false, unique = true)
    private int nr_sali;

    @Column(name = "pojemnosc")
    private int pojemnosc;

    @Column(name = "oddzial")
    private String oddzial;

    @JsonBackReference
    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.DETACH,CascadeType.MERGE,
            CascadeType.PERSIST,CascadeType.REFRESH)
    @JoinTable(
            name = "pielegniarki_sale",
            joinColumns = @JoinColumn(name = "nr_sali"),
            inverseJoinColumns = @JoinColumn(name = "id_pielegniarki")
    )
    private List<Pielegniarka> pielegniarki;


    public Sala() 
    

    public Sala(int pojemnosc, String oddzial, List<Pielegniarka> pielegniarki) 
        this.pojemnosc = pojemnosc;
        this.oddzial = oddzial;
        this.pielegniarki = pielegniarki;
    

    public void addPielegniarka(Pielegniarka pielegniarka)
        if(pielegniarki == null)
            pielegniarki = new ArrayList<>();
        
        pielegniarki.add(pielegniarka);
    

    public void removePielegniarka(Pielegniarka pielegniarka)
        pielegniarki.remove(pielegniarka);
        pielegniarka.getSale().remove(this);
    

我的 SalaDAO 中也有一个方法(以及 PielegniarkaDAO 中的类似方法),它将 Pielegniarka 添加到 Sala 类中的列表中,然后将该 Sala 的 id 和新添加的 Pielegniarko 的 id 插入到我的 Oracle 数据库中的 Join Table 中(因为 @多对多注释)

  @Override
    public void saveSalaWithIdPielegniarki(int idPielegniarki, int nr_sali) 
        Pielegniarka pielegniarka = entityManager.find(Pielegniarka.class, idPielegniarki);
        Sala sala = entityManager.find(Sala.class, nr_sali);

        if (pielegniarka != null && sala != null) 
            for (Pielegniarka salPiel : sala.getPielegniarki()) 
                if (salPiel.getId_pielegniarki() == idPielegniarki) 
                    return;
                
            
            pielegniarka.addSala(sala);
            sala.addPielegniarka(pielegniarka);
        

    

这是 SalaController 的一个片段,它显示了执行此操作的方法。在服务类中,我使用了与 DAO 中完全相同的方法。它只是 DAO 类的包装器。

@RestController
@RequestMapping("/sala")
public class SalaController 

 @PostMapping("/nr_sali/pielegniarka/idPielegniarki")
    public void saveSalaWithIdPielegniarki(@PathVariable int idPielegniarki,
                                           @PathVariable int nr_sali) 
        salaService.saveSalaWithIdPielegniarki(idPielegniarki,nr_sali);
    


当我尝试访问此端点时,例如 /sala/4/pielegniarka/5 应该将 id 为 5 的 Pielegniarka 添加到 id 为 4 的 sala 列表中,Hibernate 将 id 为 4,5 的记录两次插入到 pielegniarki_sale JOIN TABLE 中我的数据库。什么会导致此错误?

Oracle 正因此抛出“ORA-00001:违反唯一约束”。

这是显示双重插入的 Spring Boot 日志图片。与实体关系图片

Unique constraint violated error

MER

【问题讨论】:

您可以为这些表格上传一张 MER 的图片吗? 添加图片@BugsForBreakfast 嗯,从简单的角度来看,一切看起来都很好,但是由于您正在使用 SEQUENCE,所以您是否确保在您的数据库中没有重复的 id 或序列不是“因水的原因而中断?例如,id 为 28,而您的序列当前为 27,因此它尝试使用 28 并导致错误发生,请检查是否丢弃:p 【参考方案1】:

您已经两次定义了与同一个房地产表的多对多关系。

您必须将其中一种关系设为拥有,而将一种关系设为反方。

拥有方是关系中维护中间关系表的一方(插入、更新、删除),而另一方,即所谓的反向方,是在插入、更新方面没有发生任何事情的一方并删除。

当您使用指向拥有方属性的 mappedBy 属性时,定义了 inserves 方。

例如,您可以拥有这样的拥有方:

@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.DETACH,CascadeType.MERGE,
        CascadeType.PERSIST,CascadeType.REFRESH)
@JoinTable(
        name = "pielegniarki_sale",
        joinColumns = @JoinColumn(name = "nr_sali"),
        inverseJoinColumns = @JoinColumn(name = "id_pielegniarki")
)
private List<Pielegniarka> pielegniarki;

然后反面是这样的:

@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.DETACH,CascadeType.MERGE,
        CascadeType.PERSIST,CascadeType.REFRESH, mappedBy = "pielegniarki")
private List<Pielegniarka> pielegniarki;

如果您不这样做并让它像您的映射一样,那么 Hibernate 会尝试将两次相同的记录插入到关系表中。

【讨论】:

非常感谢,您的回答对我有帮助!但准确地说。当我在做 pielegniarka.add(sala) 和 sala.add(pielegniarka) 时,他们都在向 JOIN TABLE 添加记录? 是的。请查看文档docs.jboss.org/hibernate/orm/5.4/userguide/html_single/…

以上是关于Hibernate 两次将数据插入到 Join Table 中,导致 Spring Boot 项目中出现“ORA-00001:违反唯一约束”的主要内容,如果未能解决你的问题,请参考以下文章

spring2.5+hibernate3.2+struts2.0插入数据时调用两次sql语句

如何一次将数据插入和更新到 EF 多个表中

是否可以使用 Cakephp 中的 saveAll 一次将所有相关记录插入到 HABTM 中?

在 Python 中:一次将 QtableWidget 中的所有条目插入数据库表中

是否可以一次将整个 VB.NET 数据表插入 SQL Server

HQL Hibernate INNER JOIN