Spring / JPA保持一对多的子组合键,其父键为null

时间:2018-11-27 22:08:01

标签: java jpa spring-data-jpa persistence

我有以下“父母”:

@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
    ...
}

收件人“孩子”:

@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值,但是当持久存储收件人时,复合键的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,但始终为null。从我可以在网上找到的所有内容来看,这应该可行。我发现了一个或两个类似的SO问题,但是都没有解决复合键的具体问题,其中复合字段之一是父ID。

我认为我可以通过以下两种方法来解决此问题:将消息和收件人保持在两个单独的状态中(持久消息,获取ID,然后创建并保留收件人),但是据我所知,这应该是>工作...

1 个答案:

答案 0 :(得分:1)

这是因为您需要使用message_idRecipientId映射到类@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(); }
}