【问题标题】:Spring JPA/HIbernate: duplicate key value violates unique constraintSpring JPA/HIbernate:重复键值违反唯一约束
【发布时间】:2021-05-12 08:34:31
【问题描述】:

在现有答案中搜索没有得到有效结果。所以这是我找不到原因的错误。 我有一个实体类MeteoRecordDayDegree定义如下:

@Getter
@Setter
@Entity
public class MeteoRecord extends AbstractEntity {

    @Temporal(TemporalType.DATE)
    Date date;
...
//other attributes
}

@Entity
@Getter
@Setter
public class DayDegree extends AbstractEntity {

    @ManyToOne
    @JoinColumn(name = "meteo_record_id")
    private MeteoRecord meteoRecord;
...
//other attributes
}

所有实体都扩展AbstractEntity类:

@MappedSuperclass
@Getter
@Setter
public abstract class AbstractEntity implements Serializable {

    @Access(AccessType.PROPERTY)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @CreationTimestamp
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "DATCRE")
    public Date datcre;

    @UpdateTimestamp
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "DATMOD")
    public Date datmod;
}

保存新的MeteoRecord 时如下:

Optional<MeteoRecord> meteoByDate = meteoRecordRepository.findByDate(dateAsDate);
  MeteoRecord meteoRecord;
  if (meteoByDate.isPresent()) {
      meteoRecord = meteoByDate.get();
  } else {
      meteoRecord = new MeteoRecord();
      meteoRecord.setDate(dateAsDate);
  }
MeteoRecord savedMeteoRecord = meteoRecordRepository.save(meteoRecord);
...

它引发了错误:

rg.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "meteo_record_pkey"
  Detail: Key (id)=(1680) already exists.

`MeteoRepositiry 只是扩展了 `JPARepository:

@Repository
public interface MeteoRecordRepository extends JpaRepository<MeteoRecord, Long> {
...
Optional<MeteoRecord> findByDate(Date date);
}

我激活了更多日志以查看 SQL 查询,但没有看到创建新 MeteoRecord 的其他查询,也没有在其他地方插入 DayDegree 记录:

11:33:55.664 [http-nio-8080-exec-1] DEBUG org.hibernate.SQL - 
    select
        meteorecor0_.id as id1_33_,
        meteorecor0_.datcre as datcre2_33_,
        meteorecor0_.datmod as datmod3_33_,
        meteorecor0_.date as date4_33_,
        meteorecor0_.degree_day_15 as degree_d5_33_,
        meteorecor0_.degree_day_15_eq as degree_d6_33_,
        meteorecor0_.station as station7_33_,
        meteorecor0_.temp_avg as temp_avg8_33_,
        meteorecor0_.temp_avg_eq as temp_avg9_33_ 
    from
        meteo_record meteorecor0_ 
    where
        meteorecor0_.date=?
Hibernate: 
    select
        meteorecor0_.id as id1_33_,
        meteorecor0_.datcre as datcre2_33_,
        meteorecor0_.datmod as datmod3_33_,
        meteorecor0_.date as date4_33_,
        meteorecor0_.degree_day_15 as degree_d5_33_,
        meteorecor0_.degree_day_15_eq as degree_d6_33_,
        meteorecor0_.station as station7_33_,
        meteorecor0_.temp_avg as temp_avg8_33_,
        meteorecor0_.temp_avg_eq as temp_avg9_33_ 
    from
        meteo_record meteorecor0_ 
    where
        meteorecor0_.date=?
11:33:55.677 [http-nio-8080-exec-1] DEBUG o.s.d.r.c.s.TransactionalRepositoryProxyPostProcessor$CustomAnnotationTransactionAttributeSource - Adding transactional method 'save' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
11:33:55.679 [http-nio-8080-exec-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Found thread-bound EntityManager [SessionImpl(1454087429<open>)] for JPA transaction
11:33:55.680 [http-nio-8080-exec-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
11:33:55.680 [http-nio-8080-exec-1] DEBUG o.h.e.t.internal.TransactionImpl - On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
11:33:55.680 [http-nio-8080-exec-1] DEBUG o.h.e.t.internal.TransactionImpl - begin
11:33:55.681 [http-nio-8080-exec-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@27864a10]
11:33:55.696 [http-nio-8080-exec-1] DEBUG org.hibernate.engine.spi.ActionQueue - Executing identity-insert immediately
11:33:55.703 [http-nio-8080-exec-1] DEBUG org.hibernate.SQL - 
    insert 
    into
        meteo_record
        (datcre, datmod, date, degree_day_15, degree_day_15_eq, station, temp_avg, temp_avg_eq) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    insert 
    into
        meteo_record
        (datcre, datmod, date, degree_day_15, degree_day_15_eq, station, temp_avg, temp_avg_eq) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?)
11:33:55.732 [http-nio-8080-exec-1] DEBUG o.h.e.jdbc.spi.SqlExceptionHelper - could not execute statement [n/a]
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "meteo_record_pkey"
  Detail: Key (id)=(1681) already exists.

我错过了什么?我的印象是这是因为DayDegree 实体中定义的关系,但没有看到任何查询或使用DayDegree 实体。

谢谢。

【问题讨论】:

  • 您是否在一个事务中运行有问题的代码sn-p?
  • 运行代码的服务类标注@Transactional
  • 但是使用与否@Transaction注解根本不会影响错误,不管有没有它都会发生。
  • 看起来很奇怪,hibernate生成的sql中没有id字段..你如何在MeteoRecord中定义它,它是如何在数据库中管理的?
  • 因为日志中有一个Executing identity-insert immediately,我猜你正在使用GenerationType.IDENTITY,而SEQUENCEPostgreSQL中更可取。无论如何,您确定数据库是一致的并且没有其他记录具有给定的 id?

标签: hibernate spring-data-jpa


【解决方案1】:

据此:

当我检查表meteo_record时,当然,总是有一条记录 错误中指示的 id:select * from meteo_record mr 其中 mr.id = 1678;

我可以假设问题出在数据库中。 Hibernate 不会通过 id 创建新记录来查找记录,但它只需要数据库中的下一个身份。因为它IDENTITY 它将获得策略的下一个值(+1.. 或其他)。现在假设您已将增量策略定义为INCREMENT BY 1,此时当前值为 12。因此很明显,在实体持久化期间下一个值将是 13。但不知何故(例如,通过id 列的直接 DB sql 插入)带有id=13 的记录已被插入。在这种情况下,Hibernate 对此一无所知,因为身份尚未增加。我们到了。现在,任何使用身份插入的尝试都会导致我们遇到给定的异常。

我认为你应该这样做:

select max(id) + 1 from meteo_record;

并在此使用结果值 (x):

ALTER TABLE meteo_record ALTER COLUMN id RESTART WITH x;

UPD

这是meteo_record 的 DDL

CREATE TABLE public.meteo_record (     
    id bigint NOT NULL DEFAULT nextval('meteo_record_id_seq'::regclass),     
    datcre timestamp without time zone,     
    datmod timestamp without time zone,     
    date date,     
    degree_day_15 double precision,     
    degree_day_15_eq double precision,     
    station character varying(255) COLLATE pg_catalog."default",     
    temp_avg double precision,     
    temp_avg_eq double precision,     
    CONSTRAINT meteo_record_pkey PRIMARY KEY (id) 
)

因为这里我们有一个序列,所以重置它的正确命令应该是

ALTER SEQUENCE meteo_record_id_seq RESTART WITH 1973;

【讨论】:

  • 感谢您对如何重置数据库中的 ID 的回复和建议。这似乎是一个与数据相关的问题,因为一旦我从 meteo_recordday_degree 表中删除所有记录并使用最新的 DB 转储备份,一切都恢复正常
  • 回到同一个问题,我尝试执行select max(id) + 1 from meteo_record;,它给了我1973。现在,当运行ALTER TABLE meteo_record ALTER COLUMN id RESTART WITH 1973; 时,它会以ERROR: column "id" of relation "meteo_record" is not an identity column 失败,然后是SQL state: 55000。可能是什么原因?
  • 什么 DDL 为 meteo_record 生成 pgAdmin(或任何编辑器)?
  • 嗯,我用pgAdmin怎么找呢?
  • @belgoros 是的,是的。我已经更新了答案
猜你喜欢
  • 2016-09-27
  • 1970-01-01
  • 2020-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-13
  • 2020-03-27
  • 2013-06-23
相关资源
最近更新 更多