我在将简单数据结构持久化到数据库时遇到问题。
每条消息可以有多个消息接收者。我需要的只是保存在数据库 Message 和 MessageReceivers (MR) 中。 MR 有一个名为 fk_message_id 的列,它应该自动填充 message_id (M)。
在数据库 (PostgreSQL) 中,表是用 SQL 代码创建的:
CREATE TABLE public.message
(
message_id integer NOT NULL DEFAULT nextval('message_message_id_seq'::regclass),
fk_author_id integer NOT NULL,
topic text NOT NULL,
text text NOT NULL,
audit_cd timestamp without time zone NOT NULL DEFAULT now(),
audit_md timestamp without time zone,
CONSTRAINT message_pkey PRIMARY KEY (message_id)
)
CREATE TABLE public.message_receiver
(
fk_message_id integer NOT NULL,
fk_user_id integer NOT NULL,
is_read boolean NOT NULL,
read_date timestamp without time zone,
audit_cd timestamp without time zone NOT NULL DEFAULT now(),
autid_md timestamp without time zone,
CONSTRAINT message_receiver_pkey PRIMARY KEY (fk_message_id, fk_user_id),
CONSTRAINT message_receiver_fk_message_id_fkey FOREIGN KEY (fk_message_id)
REFERENCES public.message (message_id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
)
Message.java
@Entity
@Table(name="message")
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="message_message_id_seq")
@SequenceGenerator(name="message_message_id_seq", sequenceName="message_message_id_seq", allocationSize=1)
@Column(name="message_id")
private Long id;
@NotNull
@Column(name="fk_author_id")
private Long author;
@OneToMany
@Cascade(CascadeType.PERSIST)
@JoinColumn(name="fk_message_id", nullable = false)
private List<MessageReceiver> receivers;
@NotNull
@Column(name="topic")
private String topic;
@NotNull
@Column(name="text")
private String text;
@NotNull
@Column(name="audit_cd")
@Convert(converter=PersistentLocalDateTime.class)
private LocalDateTime sendDate;
...getters, setters, constructors...
}
MessageReceiver.java
@Entity
@Table(name="message_receiver")
public class MessageReceiver implements Serializable {
private static final long serialVersionUID = 2L;
@Id
@Column(name="fk_message_id")
private Long messageId;
@Id
@Column(name="fk_user_id")
private Long receiverId;
@NotNull
@Column(name="is_read")
private Boolean isRead;
@Column(name="read_date")
@Convert(converter = PersistentLocalDateTime.class)
private LocalDateTime readDate;
...getters, setters, constructors...
}
当我尝试用接收者保存消息时,我得到:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not insert: [com.example.foldermessage.model.MessageReceiver]; SQL [insert into message_receiver (is_read, read_date, fk_message_id, fk_user_id) values (?, ?, ?, ?)]; nested exception is org.hibernate.exception.DataException: could not insert: [com.example.foldermessage.model.MessageReceiver]] with root cause
org.postgresql.util.PSQLException: The column index is out of range: 5, number of columns: 4.
at org.postgresql.core.v3.SimpleParameterList.bind(SimpleParameterList.java:68) ~[postgresql-9.4.1211.jar:9.4.1211]
at org.postgresql.core.v3.SimpleParameterList.setNull(SimpleParameterList.java:157) ~[postgresql-9.4.1211.jar:9.4.1211]
at org.postgresql.jdbc.PgPreparedStatement.setNull(PgPreparedStatement.java:287) ~[postgresql-9.4.1211.jar:9.4.1211]
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:61) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:257) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:252) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.type.ComponentType.nullSafeSet(ComponentType.java:343) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrateId(AbstractEntityPersister.java:2636) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:2604) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2883) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3386) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:89) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:560) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:434) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
我使用JpaRepository进行保存操作:
@Repository
public interface MessageRepository extends JpaRepository<Message, Long> {}
插入查询似乎没问题,我看不到任何错误。
我还尝试将 MR 实体 ID 更改为带有 @IdClass 注释和 @PrimaryKeyJoinColumn 的复合键。这些尝试没有帮助。
最佳答案
我找到了临时解决方案。
首先,我启用了 DDL
spring.jpa.hibernate.ddl-auto=create (or update)
来自动生成模式。在之前的解决方案中,message_receiver 具有主键
fk_message_id, fk_user_id
。这个复合 key 的主要目的是防止向一个用户发送相同的消息两次或更多次。
我决定放置生成的值而不是复合键。可以通过在PostgreSQL级别添加UNIQUE CONSTRAINT来实现对消息唯一性的约束。
消息接收者.java
@Entity
@Table(name="message_receiver")
public class MessageReceiver implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name="message_receiver_id")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="message_receiver_id_seq")
@SequenceGenerator(name="message_receiver_id_seq", sequenceName="message_receiver_id_seq", allocationSize=1)
private Long messageReceiverId;
@NotNull
@Column(name="fk_message_id")
private Long messageId;
@NotNull
@Column(name="fk_user_id")
private Long receiverId;
@NotNull
@Column(name="is_read")
private Boolean isRead;
@Column(name="read_date")
@Convert(converter = PersistentLocalDateTime.class)
private LocalDateTime readDate;
..getters, setters..
消息.java
@Entity
@Table(name="message")
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="message_message_id_seq")
@SequenceGenerator(name="message_message_id_seq", sequenceName="message_message_id_seq", allocationSize=1)
@Column(name="message_id")
private Long id;
@NotNull
@Column(name="fk_author_id")
private Long author;
@OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
@JoinColumn(name="fk_message_id")
private List<MessageReceiver> receivers = new ArrayList<>();
@NotNull
@Column(name="topic")
private String topic;
@NotNull
@Column(name="text")
private String text;
@NotNull
@Column(name="audit_cd")
@Convert(converter=PersistentLocalDateTime.class)
private LocalDateTime sendDate;
..getters, setters..
现在,我很好奇为什么第一个解决方案不起作用以及如何让它起作用。我尝试混合使用 @PrimaryKeyJoinColumn、@IdClass、@OneToMany(mappedBy)、@JoinColumn(nullable、insertable、updatable)。
当我启用 DDL(创建)时,Hibernate 会生成与我在开头放置的表相同的表。这意味着不合适的表结构不是问题。
如果更改生成主键的策略有帮助,那么保存复合键可能会导致错误。在日志中,我经常看到 Hibernate 添加到实体 *_IdBackref
。也许它会尝试将此 ID 保存/映射到数据库中,但它的列尚未准备好。
如果我找到它,我会发布最终解决方案。
关于java - Hibernate、Spring、PostgreSQL - 列索引超出范围,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39686131/