繁体   English   中英

在Hibernate中将多个关联持久化到同一实体

[英]Persisting multiple associations to same entity in Hibernate

我正在创建一个聊天系统,并且具有以下数据库架构(与核心问题无关的所有内容均已删除)。

数据库架构

线程代表两个参与者之间的对话。 创建(持久化)新线程时,应创建两个参与者。 一个用于发送方,一个用于接收方(消息已添加到线程中,但在这种情况下不相关)。 因此,我已将两个数据库表映射到两个实体。

@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协会应包含在该线程的所有参与者,而senderreceiver包含分别向发送者和接收者,参考资料,以方便使用。 因此,数据库中仅应保留两个参与者。 这是我为保留新线程而编写的代码:

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:非null属性引用一个瞬态值-必须在当前操作之前保存瞬态实例:com.example.thread.entity.Participant.thread-> com.example.thread.entity.Thread

我在两个实体上尝试了cascade属性的多种变体,但是在所有情况下都抛出相同的异常(尽管具有不同的瞬态属性)。 从逻辑上讲,该方法应该没有问题,因为所有要做的事情是,首先将Thread实体持久化,以便参与者在自己持久化之前获得生成的ID。

我的映射是否有问题,或者是什么问题? 谢谢!

好的,事实证明,问题是由@AlanHay指出的与我的数据库约束一样明显的原因引起的。 我取消了对在非空约束sender_idreceiver_id列,并更新的代码如下。

/** Create thread **/
Thread thread = new Thread();
thread.setSubject(subject);
Thread savedThread = this.threadRepository.save(thread);


/** Save participants **/
Participant sender = new Participant();
senderParticipant.setThread(savedThread);

Participant receiver = new Participant();
companyParticipant.setThread(savedThread);

this.participantRepository.save(sender);
this.participantRepository.save(receiver);


/** Add participants to thread **/
savedThread.setSender(sender);
savedThread.setReceiver(receiver);

Set<Participant> participants = new HashSet<>(2);
participants.add(sender);
participants.add(receiver);
savedThread.setParticipants(participants);
this.threadRepository.save(savedThread);

好吧,那很尴尬,不是吗? ;-)它发生了!

根据我的评论,提供了一个更详细的答案,要求提供它作为答案。

尝试使用不同的级联类型,joincolumns和其他休眠注释解决问题。 通常是解决此类问题的错误方法。 特别是级联应该以与您使用的方式不同的方式使用,并且您现在应该将其从此映射中删除。 总是根据您要完成的用例来查看模型的语义/含义。 用线程级联参与者可能不是您想要的,因为在大多数情况下,参与者将创建并维护线程的独立性。 因此,不要尝试在没有实际业务案例的情况下使用级联简化单个实体的保存过程。

我建议您拆分它,并独立于参与者创建/保存Thread对象。 它可能需要多几行代码,但是从长远来看它们更容易理解并且更易于维护。

如上所述,原始问题是由于Thread与参与者之间的循环依赖关系导致的,因此插入到Thread中需要sender_id和receive_id可用:但是,插入参与者时需要thread_id可用。

将Thread中的FK列设置为可空可解决您的问题时,该架构似乎不太正确。 例如,在没有任何数据库触发器的情况下,通过为线程设置FK sender_id或receive_id来指向参与者,而参与者在该参与者的表中没有该特定线程的相应记录,则似乎有可能破坏参照完整性。

然后,更好的方法可能是在参与者表participant_type(发送者,接收者等)中添加一个附加列,并从线程中删除FK列。 如果线程的参与者数量总是很少,那么您可以简单地在内存中迭代集合以根据类型获取发送者和接收者。

@Entity
@Table(name = "thread")
public class Thread {

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

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "thread", cascade = CascadeType.ALL)
    private Set<Participant> participants = new HashSet<>();


    public Participant getSender(){
    //iterate and find
    }

    public Participant getReceiver(){
    //iterate and find
    }
}

如果参与者的数量很大,那么为了避免加载所有参与者来获取发送者和接收者,那么我认为另一种方法(并且我还没有对此进行测试)将是子类化,并使用一个discriminator列,看起来像这样:

@DiscriminatorColumn(name="participant_type")
public class Participant {


}

@DiscriminatorValue("S")
public class Sender extends Participant{


}

@DiscriminatorValue("R")
public class Receiver extends Participant{


}

@Entity
@Table(name = "thread")
public class Thread {

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

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "thread", cascade = CascadeType.ALL)
    //@WhereTable("....") //exclude sender and receiver ????
    private Set<Participant> participants = new HashSet<>();

    @ManyToOne
    @JoinTable(name="participant", joinColumns=@JoinColumn(name="thread_id"), inverseJoinColumns=@JoinColumn(name="participant_id"))
    private Sender sender;

    @ManyToOne
    @JoinTable(name="participant", joinColumns=@JoinColumn(name="thread_id"), inverseJoinColumns=@JoinColumn(name="participant_id"))
    private Receiver receiver;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM