【发布时间】:2015-05-11 14:14:41
【问题描述】:
我正在制作一个聊天系统,并且我有以下数据库架构(与核心问题无关的所有内容都已删除)。
线程代表两个参与者之间的对话。当一个新线程被创建(持久化)时,应该创建两个参与者;一个用于发送者,一个用于接收者(一条消息被添加到线程中,但在这种情况下不相关)。所以我已经将两个数据库表映射到两个实体。
@Entity
@Table(name = "participant")
public class Participant {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column
private int id;
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Thread.class, optional = false)
@JoinColumn(name = "thread_id")
private Thread thread;
// Getters and setters
}
@Entity
@Table(name = "thread")
public class Thread {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column
private int id;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "thread", targetEntity = Participant.class, cascade = CascadeType.ALL)
private Set<Participant> participants = new HashSet<>();
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Participant.class, cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "sender_id")
private Participant sender;
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Participant.class, cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "receiver_id")
private Participant receiver;
// Getters and setters
}
从Thread 实体中,participants 关联应包含线程中的所有参与者,而sender 和receiver 为方便起见分别包含对发送者和接收者的引用。因此,只有两个参与者应该被持久化在数据库中。这是我为持久化一个新线程而编写的代码:
Thread thread = new Thread();
Participant sender = new Participant();
sender.setThread(thread);
Participant receiver = new Participant();
receiver.setThread(thread);
thread.setSubject(subject);
thread.setSender(sender);
thread.setReceiver(receiver);
Set<Participant> participants = new HashSet<>(2);
participants.add(sender);
participants.add(receiver);
thread.setParticipants(participants);
Thread saved = this.threadRepository.save(thread);
这会引发以下异常。
org.hibernate.TransientPropertyValueException:非空属性 引用瞬态值 - 瞬态实例必须先保存 当前操作:com.example.thread.entity.Participant.thread -> com.example.thread.entity.Thread
我在两个实体上尝试了cascade 属性的许多变体,但在所有情况下都会抛出相同的异常(尽管具有不同的瞬态属性)。从逻辑上讲,该方法应该没有问题,因为必须首先持久化 Thread 实体,以便参与者获得生成的 ID,然后再将它们自己持久化。
我的映射是否有问题,或者是什么问题?谢谢!
【问题讨论】:
-
"...所要做的就是首先持久化 Thread 实体,以便参与者获得生成的 ID..."。但是,Thread 本身对发送方和接收方都没有不可为空的 FK 吗?由于您有循环关系,因此肯定两者都不能持久:插入 T 需要 sender_id 和 receiver_id 可用。:但是插入参与者需要 thread_id 可用。
-
@AlanHay 你完全正确!我不知道我是怎么没想到的。想到的第一个解决方案是允许接收者和发送者为 NULL,将线程与参与者一起保留,然后更新线程。这意味着我必须从我的数据库中删除我的非空约束并在我的代码中强制执行这个约束。鉴于逻辑包含在服务中,这并不算太糟糕,因为它降低了出错的风险。但也许有更好的方法?不管怎样,谢谢你的提醒。我只能说“哦!” :-)
-
退后一步,从业务逻辑的角度来看你在做什么。在现实中是否存在参与者将与线程同时创建的用例。我个人不这么认为。因此,将其拆分并创建线程。然后创建参与者并将他们添加到线程。不要尝试级联以使其更容易。始终将级联属性视为对模型对象如何交互的语义思考。而且不是为了方便的保存操作。
-
@mh-dev 你也完全正确!我采用了您的方法,并且更新的代码有效。随意添加答案。谢谢! :-)
-
加了一点糖作为答案。