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 实例:meet
和 m
。
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()
方法返回的实例,则更改(在本例中为日期)将不会保存到数据库中。
- Hibernate 通过 ID 识别对象。
- Hibernate 将仅为临时实体实例在数据库中插入新记录。
这意味着 meet
中包含的 Meetingsattachment
没有正确的 id(全部为零,这似乎是基本类型的默认 unsaved-value),因此Hibernate 认为它们是暂时的(新的)。
因此,解决方案是提供正确的 ID 并处理分离的对象,或者像您在解决方法中所做的那样手动合并集合。
我有以下会议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 实例:meet
和 m
。
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()
方法返回的实例,则更改(在本例中为日期)将不会保存到数据库中。
- Hibernate 通过 ID 识别对象。
- Hibernate 将仅为临时实体实例在数据库中插入新记录。
这意味着 meet
中包含的 Meetingsattachment
没有正确的 id(全部为零,这似乎是基本类型的默认 unsaved-value),因此Hibernate 认为它们是暂时的(新的)。
因此,解决方案是提供正确的 ID 并处理分离的对象,或者像您在解决方法中所做的那样手动合并集合。