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

2 Applications using different hibernate versions but same oracle database throwing unique constraint error

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

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

另一个应用程序正在使用 Spring 引导 - 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 数据库。

这 2 个应用程序使用了许多相似的实体,这些实体被复制到另一个 application/project。

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

分析时,我们发现第一个应用程序 (1.5.18.Release) 突然递增序列,虽然它应该递增 1,但在两者之间留下很多间隙,有时是 50、100,等等 和 当第二个应用程序(2.4.1)尝试插入记录时,存在唯一约束错误。

求助,请问具体哪里可以查到根本原因,或者这种情况下hibernate的缓存机制是怎么用的?

第一次申请的实体之一(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;

数据库序列为:

创建序列“MERCURY_INSTANCE_SEQ” MINVALUE 1 MAXVALUE 999999999999999999999999999 递增 1 START WITH 55 NOCACHE NOORDER NOCYCLE ;

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

此值往往取决于您的应用程序是读取密集型还是写入密集型。此外,您还必须考虑性能(如果它经常写入数据库,较低的值可能会导致性能问题)。如果您的应用程序是只读应用程序,那么使用分配大小 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 将增加这个值,而是一个数字,之后将再次进行数据库查询以获取下一个数据库序列值。在应用程序方面,除非我们达到 allocationSize 限制,否则实体实例的 ID 将始终增加 1。到达'allocationSize'后,将再次从数据库序列中检索下一个id。如果应用程序在达到 allocationSize 限制之前重新启动或重新部署,我们将看到下一个值的一次性跳转。 'allocationSize'是为了提高性能。

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

在你的第二种情况下: 尝试将 SequenceGenerator 放在 class 的顶部,以使休眠选择正确的序列。

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


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