yahh
yahh

Reputation: 1195

jpa many-to-one association creating duplicate on update

i have the following meetings 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;
}}

and i have this meetings attachment table which stores the location of the attachments

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;
    }  
} 

i am calling this method to update

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

        //fileuploading code removed from here for troubleshooting


        mrm.updateMeeting(meeting); 



        return "meetings?faces-redirect=true";


    }

inside my entity manager

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();


    }

now my problem is that when i update the entry of meeting it is duplicating the already inserted values in meeting attachment

example: lets say i have a meeting with id 27 and in meeting attachment i have the following

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

now when i try to edit meeting 27 and call the updatebutton method without changing anything or attaching any new attachments in the meetings attachment table the following happens

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

i cant figure out what i am doing wrong that meeting attachments is getting added again into the database

from the answers below i have understood the problem is the meetingattachments not being in the persistant context but i am still not sure how to solve this

current solution that is working for me but i do not believe it is ideal

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();


}

Upvotes: 2

Views: 1390

Answers (3)

ujulu
ujulu

Reputation: 3309

I think the reason is that you have two different instances of Meeting class: meetand m.

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

loads m into the persistence context. Because the relationship is one-to-manythe attachments will be loaded lazily. And therefore they are not in the persistence context. Now when you assign the list of attachments in

m.setMeetingsattachments(meet.getMeetingsattachments());

they will be considered as new entries and will be persisted. That should be the reason why you are getting duplicates.

So the problem should be solved if the updateMeeting() method is implemented as follows:

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

The only case where you need another reference like mis if you want to change the state of the managed entity in the updateMeeting() method, for example:

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

The reason is that meet will not be the managed instance. The persistence provider will create a new instance in the persistence context and copy the state of meetto it, and then that is what will be saved to the database when the emis flushed or the transaction commits. So if you don't use the returned instance from the merge() method the change (in this case the date) will not be saved to the DB.

Upvotes: 1

Dragan Bozanovic
Dragan Bozanovic

Reputation: 23552

  1. Hibernate identifies objects by their ids.
  2. Hibernate will insert new records in the db for transient entity instances only.

That means that Meetingsattachments contained in the meet do not have the proper ids (all are zero, which seems to be the default unsaved-value for primitive types), thus Hibernate considers them transient (new).

So, the solution is to provide proper ids and handle detached objects, or merge the collection manually as you are already doing in your workaround.

Upvotes: 0

RafToTheK
RafToTheK

Reputation: 86

Unfortunately a wild guess, but are you sure about your equals method in your Meetingsattachment class? Looks like it will always return false => jpa won't find an attachment, so it will create a new one.

Edit: Some code

@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;
}}

Upvotes: 0

Related Questions