JPA @SequenceGenerator 注释如何工作

Posted

技术标签:

【中文标题】JPA @SequenceGenerator 注释如何工作【英文标题】:How does the JPA @SequenceGenerator annotation work 【发布时间】:2011-02-05 09:52:20 【问题描述】:

我正在学习 JPA,对 @SequenceGenerator 注释感到困惑。

据我了解,它会自动为实体的数字身份字段/属性分配一个值。

Q1.这个序列生成器是利用数据库不断增加的数值生成能力还是自己生成数字?

Q2.如果 JPA 使用数据库自​​动增量功能,那么它是否适用于没有自动增量功能的数据存储?

Q3. 如果 JPA 自己生成数值,那么 JPA 实现如何知道接下来要生成哪个值?它是否首先咨询数据库以查看最后存储了什么值以生成值 (last + 1)?


Q4。还请说明@SequenceGenerator 注释的sequenceNameallocationSize 属性。

【问题讨论】:

【参考方案1】:

sequenceName 是数据库中序列的名称。这是您指定数据库中已存在的序列的方式。如果你走这条路,你必须指定allocationSize,它需要与DB序列用作其“自动增量”的值相同。

用法:

@GeneratedValue(generator="my_seq")
@SequenceGenerator(name="my_seq",sequenceName="MY_SEQ", allocationSize=1)

如果你愿意,你可以让它为你创建一个序列。但要做到这一点,您必须使用 SchemaGeneration 来创建它。为此,请使用:

@GeneratedValue(strategy=GenerationType.SEQUENCE)

此外,您还可以使用自动生成,它将使用表格来生成 ID。在使用此功能时,您还必须在某些时候使用 SchemaGeneration,以便可以创建生成器表。为此,请使用:

@GeneratedValue(strategy=GenerationType.AUTO)

【讨论】:

JPA 提供者(例如 Hibernate)将使用序列值作为基础,并将其与 allocationSize mutiply 以获得它将插入的实际 ID。因此,如果下一个 seq 值为 11,allocationSize 为 20,则生成的下一个 ID 将是 220。通常,您只希望您的 ID 完全遵循序列值,因此设置 allocationSize = 序列的 INCREMENT BY。另见***.com/questions/5346147/…【参考方案2】:

我用这个,效果很好

@Id
@GeneratedValue(generator = "SEC_ODON", strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "SEC_ODON", sequenceName = "SO.SEC_ODON",allocationSize=1)
@Column(name="ID_ODON", unique=true, nullable=false, precision=10, scale=0)
public Long getIdOdon() 
    return this.idOdon;

【讨论】:

【参考方案3】:

尽管这个问题已经很老了,我偶然发现它是因为我自己在 JPA 2.0 和 Oracle 序列方面的问题。

想分享我对一些事情的研究 -

GenerationType.SEQUENCE@SequenceGenerator(allocationSize)与数据库序列定义中INCREMENT BY的关系

确保 @SequenceGenerator(allocationSize) 设置为与数据库序列定义中的 INCREMENT BY 相同的值以避免出现问题(同样适用于初始值)。

例如,如果我们在数据库中定义序列的 INCREMENT BY 值为 20,则将 SequenceGenerator 中的分配大小也设置为 20。在这种情况下,JPA 将不会调用数据库,直到它达到下一个 20 标记,而它在内部将每个值增加 1。这节省了每次获取下一个序列号的数据库调用。 这样做的副作用是 - 每当重新部署应用程序或重新启动服务器时,它都会调用数据库来获取下一批,您会看到序列值的跳跃。此外,我们需要确保数据库定义和应用程序设置保持同步,这可能无法始终保持同步,因为它们都由不同的组管理,您很快就会失去控制。如果数据库值小于分配大小,您将看到由于 Id 值重复而导致的 PrimaryKey 约束错误。如果数据库值高于分配大小,您将看到 Id 值的跳跃。

如果数据库序列 INCREMENT BY 设置为 1(这是 DBA 通常所做的),请将 allocationSize 也设置为 1,以便它们同步,但 JPA 每次调用数据库以获取下一个序列号。

如果您不希望每次都调用数据库,请使用 GenerationType.IDENTITY 策略并通过数据库触发器设置 @Id 值。使用 GenerationType.IDENTITY,只要我们调用 em.persist,对象就会保存到 DB,并且 id 的值会分配给返回的对象,因此我们不必做一个 em.mergeem.flush。 (这可能是 JPA 提供程序特定的..不确定)

另一件重要的事 -

JPA 2.0 自动运行 ALTER SEQUENCE 命令以同步数据库序列中的 allocationSize 和 INCREMENT BY。由于大多数情况下我们使用不同的 Schema 名称(应用程序用户名)而不是序列存在的实际 Schema 并且应用程序用户名将没有 ALTER SEQUENCE 权限,您可能会在日志中看到以下警告 -

000004c1 运行时 W CWWJP9991W:openjpa.Runtime:警告:无法 缓存序列“RECORD_ID_SEQ”的序列值。您的 应用程序无权运行 ALTER SEQUENCE 命令。 确保它具有运行 ALTER SEQUENCE 的适当权限 命令。

由于 JPA 无法更改序列,因此无论@SequenceGenerator.allocationSize 的值如何,JPA 每次都会调用数据库以获取下一个序列号。这可能是我们需要注意的不良后果。

要让 JPA 不运行此命令,请在 persistence.xml 中设置此值。这确保 JPA 不会尝试运行 ALTER SEQUENCE 命令。不过它会写一个不同的警告 -

00000094 运行时 W CWWJP9991W:openjpa.Runtime:警告: 属性“openjpa.jdbc.DBDictionary=disableAlterSeqenceIncrementBy”是 设置为真。这意味着 'ALTER SEQUENCE...INCREMENT BY' SQL 将不会为序列“RECORD_ID_SEQ”执行语句。 OpenJPA 执行此命令以确保序列的 INCREMENT BY 值 数据库中定义的与定义在 实体的顺序。禁用此 SQL 语句后,它是 用户有责任确保实体的顺序 定义与数据库中定义的序列匹配。

如警告中所述,重要的是我们需要确保数据库序列定义中的@SequenceGenerator.allocationSize 和INCREMENT BY 同步,包括@SequenceGenerator(allocationSize) 的默认值50。否则会导致错误。

【讨论】:

【参考方案4】:

现在,回到你的问题:

第一季度。这个序列生成器是否利用了数据库的增加 数值生成能力或在其上生成数字 拥有?

通过在@GeneratedValue 注释上使用GenerationType.SEQUENCE 策略,JPA 提供程序将尝试使用支持此功能的底层数据库(例如,Oracle、SQL Server、PostgreSQL、MariaDB)的数据库序列对象。

如果您使用的是不支持数据库序列对象的 mysql,那么 Hibernate 将转而使用 GenerationType.TABLE,这是不可取的,因为 TABLE 生成性能很差。

所以,不要在 MySQL 中使用GenerationType.SEQUENCE 策略。

第二季度。如果 JPA 使用数据库自​​动增量功能,那么它会起作用吗 使用没有自动增量功能的数据存储?

当你说database auto-increment feature 时,我假设你在谈论GenerationType.IDENTITY

要使用AUTO_INCREMENTIDENTITY 列,您需要在@GeneratedValue 注释上使用GenerationType.IDENTITYstrategy。

第三季度。如果 JPA 自己生成数值,那么 JPA 如何 实现知道接下来要生成哪个值?是否咨询 数据库首先查看最后存储了什么值,以便 生成值 (last + 1)?

JPA 提供程序自己生成值的唯一时间是您使用基于序列的优化器时,例如:

hi/lo pooled or pooled-lo

这些优化器用于减少数据库序列调用的数量,因此它们将使用单个数据库序列调用可以生成的标识符值的数量相乘。

为避免 Hibernate 标识符优化器与其他第 3 方客户端发生冲突,您应该使用 pooledpooled-lo 而不是 hi/lo。即使您使用的是设计为使用 hi/lo 的旧版应用程序,您也可以migrate to the pooled or pooled-lo optimizers。

第四季度。还请说明sequenceNameallocationSize @SequenceGenerator 注解的属性。

sequenceName 属性定义用于生成标识符值的数据库序列对象。它是您使用CREATE SEQUENCE DDL 语句创建的对象。

因此,如果您提供此映射:

@Id
@GeneratedValue(
    strategy = GenerationType.SEQUENCE,
    generator = "seq_post"
)
@SequenceGenerator(
    name = "seq_post"
)
private Long id;

Hibernate 将使用 seq_post 数据库对象来生成标识符值:

SELECT nextval('hibernate_sequence')

allocationSize 定义了标识符值乘数,如果您提供的值大于 1,那么 Hibernate 将使用pooled 优化器,以减少数据库序列调用的次数。

因此,如果您提供此映射:

@Id
@GeneratedValue(
    strategy = GenerationType.SEQUENCE,
    generator = "seq_post"
)
@SequenceGenerator(
    name = "seq_post",
    allocationSize = 5
)
private Long id;

然后,当你持久化 5 个实体时:

for (int i = 1; i <= 5; i++) 
    entityManager.persist(
        new Post().setTitle(
            String.format(
                "High-Performance Java Persistence, Part %d",
                i
            )
        )
    );

只会执行 2 个数据库序列调用,而不是 5 个:

SELECT nextval('hibernate_sequence')
SELECT nextval('hibernate_sequence')
 
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence, Part 1', 1)
 
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence, Part 2', 2)
 
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence, Part 3', 3)
 
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence, Part 4', 4)
 
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence, Part 5', 5)

【讨论】:

【参考方案5】:

我有带有 autogen 值的 MySQL 架构。我使用strategy=GenerationType.IDENTITY 标签并且似乎在 MySQL 中工作正常我想它也应该适用于大多数数据库引擎。

CREATE TABLE user (
    id bigint NOT NULL auto_increment,
    name varchar(64) NOT NULL default '',
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

User.java:

// mark this JavaBean to be JPA scoped class
@Entity
@Table(name="user")
public class User 
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;    // primary key (autogen surrogate)

    @Column(name="name")
    private String name;

    public long getId()  return id; 
    public void setId(long id)  this.id = id; 

    public String getName()  return name; 
    public void setName(String name)  this.name=name; 

【讨论】:

为了简洁起见,我认为最好的方法是,因为您的 DBMS 会处理生成的值。在 Postgresql 中,该字段应该有一个默认值:default nextval('your_sequence_name')

以上是关于JPA @SequenceGenerator 注释如何工作的主要内容,如果未能解决你的问题,请参考以下文章

JPA SequenceGenerator 和 GeneratedValue:名称/生成器属性仅每个类唯一?

Hibernate 模式参数在 @SequenceGenerator 注释中不起作用

JPA 或 Hibernate 生成(非主键)列值,不是从 1 开始

hibernate 注解之 SequenceGenerator

JPA主键生成策略

Spring JPA 主键