IrishHayZeus
IrishHayZeus

Reputation: 73

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

I have the following '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
    ...
}

And the recipient '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(); }
}

With the composite key:

@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);
    }
}

I have the following test code:

@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);
}

When executing makeMessage() I can see in the debug output that Message gets a generated MessageId value, but when persisting Recipients, the messageId of the composite key is null, causing an failure of the transaction.

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

I've exhausted about every possibility I can think of for trying to get this to work, including @IdClass, but its always null. From everything I can find online, this should work. I found one or two similar SO questions, but none addressed the specifics of a Composite Key where one of the composite fields is the parents Id.

I figure I can work around this by persisting the Message and the Recipients in two separate states (persist Message, get the ID and then create and persist the Recipients) but from what I know, this should work...

Upvotes: 1

Views: 1681

Answers (1)

Marco
Marco

Reputation: 587

That is because you need to map the message_id into the class RecipientId with @MapsId("messageId") you can do the following:

@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(); }
}

Upvotes: 1

Related Questions