2 使用不同休眠版本但相同oracle 数据库抛出唯一约束错误的应用程序

问题描述

有 2 个应用程序:一个是使用 Spring boot - 1.5.18.Release 版本,其中 hibernate 版本为 5.0.12.Final

:https://search.maven.org/artifact/org.springframework.boot/spring-boot-dependencies/1.5.18.RELEASE/pom

一个应用程序正在使用 Spring boot - 2.4.1 版本,它的休眠版本为 5.4.25.Final :https://search.maven.org/artifact/org.springframework.boot/spring-boot-dependencies/2.4.1/pom 我们在这里使用了@SequenceGenerator(name = "sequenceGenerator",sequenceName = "ABCD_SEQ",allocationSize = 1),

需要分配大小,因为应用程序没有启动

虽然在第一次申请中不需要分配大小。

数据库序列是用“INCREMENT BY 1”创建的,两个应用程序使用相同的oracle数据库

这两个应用程序使用了许多相似的实体,这些实体被复制到另一个应用程序/项目中。

但是当从 spring-boot 为 2.4.1 的第二个应用程序插入记录时,我们遇到了独特的序列生成器问题。

经过分析,我们发现第一个应用程序(1.5.18.Release)突然增加了序列,尽管它应该将其增加 1,但中间留下了很多差距,有时增加 50、100 等。 和 当第二个应用程序(2.4.1)尝试插入记录时,出现唯一约束错误

请帮忙,究竟在哪里寻找根本原因,或者这种情况下如何使用休眠缓存机制?

一个应用程序(1.5.18.Release)中的一个实体

@Entity
@Table(name = "MERCURY_INSTANCE")
public class MercuryInstance implements Serializable {

    @Id
    @Column(name = "MERCURY_INSTANCE_KEY",nullable = false)
    @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "seqGen")
    @SequenceGenerator(name = "seqGen",sequenceName = "MERCURY_INSTANCE_SEQ")
    private Long mercuryInstanceKey;

    @ManyToOne(cascade = CascadeType.DETACH,fetch = FetchType.LAZY)
    @JoinColumn(name = "MERCURY_KEY",referencedColumnName = "MERCURY_KEY",nullable = false)
    private MERCURY mercury;

    @OnetoOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
    @JoinColumn(name = "MERCURY_INSTANCE_KEY",referencedColumnName = "MERCURY_INSTANCE_KEY",nullable = false)
    private MercuryInstanceTechParams MercuryInstanceTechParams;

    @ManyToMany(mappedBy = "mercuryGroupInstances")
    private List<MercuryGroupInstance> MercuryGroupInstances;
    
    @Column(name = "CREATED_DATE")
    private Timestamp createdDte;
    @Column(name = "CREATED_BY")
    private String createdBy;
    @Column(name = "UPDATED_DATE")
    private Timestamp updatedDte;
    @Column(name = "UPDATED_BY")
    private String updatedBy;
    /* getter and setters of above fields */

}

虽然另一个应用程序(2.4.1)类似,唯一不同的是序列生成器,例如:

@Id
@Column(name = "MERCURY_INSTANCE_KEY",nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "seqGen")
@SequenceGenerator(name = "seqGen",sequenceName = "MERCURY_INSTANCE_SEQ",allocationSize = 1)
private Long mercuryInstanceKey;

数据库序列为:

CREATE SEQUENCE "MERCURY_INSTANCE_SEQ" MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 55 NOCACHE NOORDER NOCYCLE ;

解决方法

@SequenceGenerator 默认分配大小为 50。首先,最佳做法是将分配大小与数据库序列中的 INCREMENT BY 值保持一致

该值往往取决于您的应用程序是读取密集型还是写入密集型。此外,您还必须考虑性能(如果它经常写入 db,则较低的值可能会导致性能问题)。如果您的应用程序是只读应用程序,则使用分配大小 1000 或 1 的影响可以忽略不计。

下一代 Sequence Id 基于 allocationSize。

例如,第一个应用程序请求 1-50 的 id,第二个应用程序在每次插入时询问。在这种情况下,第一个应用程序将使用 1-50 之间的 id,但随后第二个应用程序请求一个 id,并将获得一个 1-50 范围内的 id(自 INCREMENT BY=1 起)。这会导致异常,哪个应用程序将使用相同的 ID 保存第二个。

因此,最简单的解决方案是更改:

@SequenceGenerator(name = "seqGen",sequenceName = "MERCURY_INSTANCE_SEQ")

第一个应用:

@SequenceGenerator(name = "seqGen",sequenceName = "MERCURY_INSTANCE_SEQ",allocationsize=1)

,

'allocationSize' 并不意味着实体 id 将增加此值,而是一个数字,之后将再次进行数据库查询以获取下一个数据库序列值。在应用程序方面,实体实例的 id 将始终增加 1,除非我们达到 allocationSize 限制。达到 'allocationSize' 后,将再次从数据库序列中检索下一个 id。如果应用程序在达到 allocationSize 限制之前重新启动或重新部署,我们将看到下一个值的一次性跳转。 'allocationSize' 是为了提高性能。

在您的第一个场景中: 序列生成器是一致的。唯一的任务是生成唯一的整数值,没有别的。 如前所述,这种行为是由 oracle 缓存、预分配、序列号(默认为 20)引起的。 ID 列是代理/人工主键,仅用于唯一标识行,不应从中派生任何信息。即使您不缓存序列号,由于事务回滚、删除、应用程序和数据库服务器重新启动,您也永远不会获得一系列不间断的 ID。并且不缓存序列对大容量事务系统会造成严重的性能损失。

在您的第二种情况下: 尝试将 SequenceGenerator 放在类的顶部,以使 hibernate 获取正确的序列。

@Entity
@Table(name = "{your_table_name}")
@SequenceGenerator(name = "seqGen",allocationSize = 1)
public class {$your class name$} {


@Id
@Column(name = "MERCURY_INSTANCE_KEY",nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "seqGen")
private Long mercuryInstanceKey;