jpa many-to-one 关联在更新时创建副本

jpa many-to-one association creating duplicate on update

我有以下会议table

 package ng.telecomroadmap.model;

import java.io.Serializable;
import javax.persistence.*;
import java.util.Date;
import java.util.List;


/**
 * The persistent class for the meetings database table.
 * 
 */
@Entity
@Table(name="meetings")
@NamedQuery(name="Meeting.findAll", query="SELECT m FROM Meeting m")
public class Meeting implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int meetingsid;

    @Temporal(TemporalType.DATE)
    private Date date;

    private String description;

    //bi-directional many-to-one association to Meetingsattachment
    @OneToMany(mappedBy="meeting", cascade = CascadeType.PERSIST)
    private List<Meetingsattachment> meetingsattachments;

    public Meeting() {
    }

    public int getMeetingsid() {
        return this.meetingsid;
    }
public void setMeetingsid(int meetingsid) {
    this.meetingsid = meetingsid;
}

public Date getDate() {
    return this.date;
}

public void setDate(Date date) {
    this.date = date;
}

public String getDescription() {
    return this.description;
}

public void setDescription(String description) {
    this.description = description;
}

public List<Meetingsattachment> getMeetingsattachments() {
    return this.meetingsattachments;
}

public void setMeetingsattachments(List<Meetingsattachment> meetingsattachments) {
    this.meetingsattachments = meetingsattachments;
}

public Meetingsattachment addMeetingsattachment(Meetingsattachment meetingsattachment) {
    getMeetingsattachments().add(meetingsattachment);
    meetingsattachment.setMeeting(this);

    return meetingsattachment;
}

public Meetingsattachment removeMeetingsattachment(Meetingsattachment meetingsattachment) {
    getMeetingsattachments().remove(meetingsattachment);
    meetingsattachment.setMeeting(null);

    return meetingsattachment;
}}

我有这个会议附件 table,它存储附件的位置

package ng.telecomroadmap.model;

import java.io.Serializable;

import javax.persistence.*;




/**
 * The persistent class for the meetingsattachment database table.
 * 
 */
@Entity
@NamedQuery(name="Meetingsattachment.findAll", query="SELECT m FROM Meetingsattachment m")
public class Meetingsattachment implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int maid;

    private String attachment;

    //bi-directional many-to-one association to Meeting
    @ManyToOne
    @JoinColumn(name="meetingsid")
    private Meeting meeting;

    public Meetingsattachment() {
    }

    public int getMaid() {
        return this.maid;
    }

    public void setMaid(int maid) {
        this.maid = maid;
    }

    public String getAttachment() {
        return this.attachment;
    }

    public void setAttachment(String attachment) {
        this.attachment = attachment;
    }

    public Meeting getMeeting() {
        return this.meeting;
    }

    public void setMeeting(Meeting meeting) {
        this.meeting = meeting;
    }
    @Override
    public int hashCode() {

        final int prime = 31;
        int result = 1;
        result = prime * result + attachment.hashCode();
        return result;
    } 

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof Meetingsattachment))
            return false;
        Meetingsattachment other = (Meetingsattachment) obj;
        if (!(other.getAttachment()).equals(attachment))
            return false; 
        return true;
    }  
} 

我正在调用这个方法来更新

public String updateButton(){
        List<Meetingsattachment> mal= meeting.getMeetingsattachments();

        //fileuploading code removed from here for troubleshooting


        mrm.updateMeeting(meeting); 



        return "meetings?faces-redirect=true";


    }

在我的实体经理里面

public void updateMeeting(Meeting meet){
        Meeting m=em.find(Meeting.class, meet.getMeetingsid());
        m.setDate(meet.getDate());
        m.setDescription(meet.getDescription());
        m.setMeetingsattachments(meet.getMeetingsattachments());

        em.flush();


    }

现在我的问题是,当我更新会议条目时,它复制了会议附件中已经插入的值

示例:假设我有一个 ID 为 27 的会议,在会议附件中我有以下内容

31  /path/to/files/t22-1700585698/1.pdf 27

现在,当我尝试编辑会议 27 并调用 updatebutton 方法而不更改任何内容或在会议附件中附加任何新附件时 table 会发生以下情况

31  /path/to/files/t22-1700585698/1.pdf 27
32  /path/to/files/t22-1700585698/1.pdf 27

我不知道我做错了什么会议附件被再次添加到数据库中

从下面的答案中我了解到问题是会议附件不在持久上下文中,但我仍然不确定如何解决这个问题

当前对我有用的解决方案,但我认为它不理想

public void updateMeeting(Meeting meet){
    Meeting m=em.find(Meeting.class, meet.getMeetingsid());

    m.setDate(meet.getDate());
    m.setDescription(meet.getDescription());
    List<Meetingsattachment> mal = m.getMeetingsattachments();
    for(Meetingsattachment ma:meet.getMeetingsattachments()){
        if(!mal.contains(ma)){
            m.addMeetingsattachment(ma);
        }
    }
    //m.setMeetingsattachments(meet.getMeetingsattachments());

    em.flush();


}

不幸的是,这是一个大胆的猜测,但您确定 Meetingsattachment class 中的 equals 方法吗?看起来它总是 return false => jpa 找不到附件,所以它会创建一个新的。

编辑:一些代码

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (!(obj instanceof Meetingattachment))
        return false;
    String other = (Meetingattachment) obj.getAttachment;
    if (!other.equals(attachment))
        return false; 
    return true;
}}

我认为原因是您有两个不同的 Meeting class 实例:meetm

Meeting m=em.find(Meeting.class, meet.getMeetingsid());

m 加载到持久性上下文中。因为关系是one-to-many所以附件会延迟加载。因此它们不在持久性上下文中。现在,当您在

中分配附件列表时
m.setMeetingsattachments(meet.getMeetingsattachments());

它们将被视为新条目并将被保留。 这应该是您重复的原因。

所以updateMeeting()方法按如下实现应该可以解决问题:

public void updateMeeting(Meeting meet){
    em.merge(meet);
    em.flush();
}

唯一需要像 m 这样的引用的情况是,如果您想在 updateMeeting() 方法中更改托管实体的状态,例如:

public void updateMeeting(Meeting meet){
    Meeting m = em.merge(meet);
    m.setDate(<some_date>);
    em.flush();
}

原因是meet将不是托管实例。持久性提供者将在持久性上下文中创建一个新实例并将 meet 的状态复制到它,然后当 em 被刷新或事务提交时,它将被保存到数据库中。因此,如果您不使用 merge() 方法返回的实例,则更改(在本例中为日期)将不会保存到数据库中。

  1. Hibernate 通过 ID 识别对象。
  2. Hibernate 将仅为临时实体实例在数据库中插入新记录。

这意味着 meet 中包含的 Meetingsattachment 没有正确的 id(全部为零,这似乎是基本类型的默认 unsaved-value),因此Hibernate 认为它们是暂时的(新的)。

因此,解决方案是提供正确的 ID 并处理分离的对象,或者像您在解决方法中所做的那样手动合并集合。