java - Hibernate、Spring、PostgreSQL - 列索引超出范围

标签 java postgresql hibernate spring-data-jpa

我在将简单数据结构持久化到数据库时遇到问题。

每条消息可以有多个消息接收者。我需要的只是保存在数据库 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 的复合键。这些尝试没有帮助。

最佳答案

我找到了临时解决方案。

  1. 首先,我启用了 DDL spring.jpa.hibernate.ddl-auto=create (or update) 来自动生成模式。

  2. 在之前的解决方案中,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/

相关文章:

node.js - Sequelize 不在 postgresql 中创建任何表

sql - Postgresql:排除文本中特定字符未以空格开头的记录

java - 使用 hibernate 4 进行延迟加载初始化

java - 以编程方式使用嵌入式 tomcat 7 配置 hibernate

java - Java list 文件中行的顺序

java - 集群环境下的Spring单例

sql - INSERT RETURNING 是否保证返回行的顺序

java - session.save 不返回持久化对象,只返回标识符

java - spring-integration-aws java配置

Java套接字权限: applet isn't initialized