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(); }
}
我有以下 '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(); }
}