Spring/JPA 保留一对多,父项的键为空的子复合键

Spring/JPA Persisting One-To-Many, Child Composite Key with key from parent is null

我有以下 'Parent':

@Entity
@Table(name = "messages")
@SequenceGenerator(name = "messageSequence", sequenceName = "message_sequence")
public class Message {
    @Id
    @Column(name = "message_id")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "messageSequence")
    Long messageId;

    @OneToMany(mappedBy = "message", cascade=CascadeType.ALL)
    List<Recipient> recipients = new ArrayList<>();

    public void addRecipient(Recipient r) {
        r.setMessage(this);
        recipients.add(r);
    }

    @Column(name = "message_body")
    String body;

    // Constructors/Getters/Setters
    ...
}

收件人'Child':

@Entity
@Table(name = "message_recipients")
public class Recipient {
    @EmbeddedId
    private RecipientId recipientId;

    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "message_id")
    private Message message;
    public void setMessage(Message msg) {
        this.message = msg;
    }

    public Recipient(Message msg, String name) {
        this.id = new RecipientId(msg, name);
        this.message = msg;
    }

    @Override
    public boolean equals(Object o) { ... }

    @Override
    public int hashCode(Object o) { return key.hashCode(); }
}

使用组合键:

@Embeddable
public class RecipientId implements Serializable {
    @Column(name = "message_id")
    Long messageId;

    @Column(name = "message_to")
    String messageTo;

    @Column(name = "message_timestamp")
    LocalDateTime messageTimestamp;

    public RecipientId(Message msg, String messageTo) {
        this.messageId = msg.messageId;
        this.messageTo = messageTo;
        this.messageTimestamp = LocalDateTime.now();
    }

    @Override
    public boolean equals(Object o) { ... }

    @Override
    public int hashCode() {
        return Objects.hash(messageId, messageTo, messageTimestamp);
    }
}

我有以下测试代码:

@Bean
public void makeMessage(MessageRepository messages) {
    Message newMsg = new Message();
    newMsg.addRecipient(new Recipient(newMsg, "recipientName"));
    newMsg.setBody("This is a test message");
    messages.save(newMsg);
}

在执行makeMessage()时,在调试输出中可以看到Message得到了生成的MessageId值,但是在持久化Recipients时,复合键的messageId为null,导致交易失败。

2018-11-27 14:49:54.158 DEBUG 2791 --- [  restartedMain] org.hibernate.SQL                        : 
Hibernate: 
values
    nextval for amessage_sequence
2018-11-27 14:49:54.190 DEBUG 2791 --- [  restartedMain] org.hibernate.SQL                        : 
Hibernate: 
    insert 
    into
        messages
        (body, message_code) 
    values
        (?, ?)
2018-11-27 14:49:54.191 TRACE 2791 --- [  restartedMain] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [This is a test message]
2018-11-27 14:49:54.193 TRACE 2791 --- [  restartedMain] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [2922079]

2018-11-27 14:49:54.196 DEBUG 2791 --- [  restartedMain] org.hibernate.SQL                        : 
Hibernate: 
    insert 
    into
        message_recipient
        (timestamp, message_id, message_to) 
    values
        (?, ?, ?)

2018-11-27 14:49:54.196 TRACE 2791 --- [  restartedMain] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [TIMESTAMP] - [2018-11-27 14:49:54.141]
2018-11-27 14:49:54.196 TRACE 2791 --- [  restartedMain] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [null]
2018-11-27 14:49:54.196 TRACE 2791 --- [  restartedMain] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [RecipientName]
2018-11-27 14:49:54.199  WARN 2791 --- [  restartedMain] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: -407, SQLState: 23502
2018-11-27 14:49:54.199 ERROR 2791 --- [  restartedMain] o.h.engine.jdbc.spi.SqlExceptionHelper   : DB2 SQL Error: SQLCODE=-407, SQLSTATE=23502, SQLERRMC=TBSPACEID=7, TABLEID=21, COLNO=1, DRIVER=4.19.26

我已经用尽了所有我能想到的尝试让它工作的可能性,包括@IdClass,但它总是空的。从我在网上可以找到的所有内容来看,这应该可行。我发现了一两个类似的 SO 问题,但 none 解决了复合键的细节,其中复合字段之一是父 ID。

我认为我可以通过将消息和收件人保持在两个不同的状态(保持消息、获取 ID 然后创建并保持收件人)来解决这个问题,但据我所知,这 应该 工作...

这是因为您需要将 message_id 映射到 class RecipientId@MapsId("messageId") 您可以执行以下操作:

@Entity
@Table(name = "message_recipients")
public class Recipient {
    @EmbeddedId
    private RecipientId recipientId;

    @MapsId("messageId")
    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "message_id")
    private Message message;
    public void setMessage(Message msg) {
        this.message = msg;
    }

    public Recipient(Message msg, String name) {
        this.id = new RecipientId(msg, name);
        this.message = msg;
    }

    @Override
    public boolean equals(Object o) { ... }

    @Override
    public int hashCode(Object o) { return key.hashCode(); }
}