簡體   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