Hibernate 6 中序列的命名策略

1、简介

本文将带你了解如何为数据库序列(Sequences)配置 Hibernate 6 的隐式 命名策略。Hibernate 6 引入了几种新的命名策略,这些策略会影响序列的命名和使用方式。

2、标准命名策略

默认情况下,Hibernate 6 使用标准命名策略。它根据实体名称和列名称生成序列名称。假如,我们有一个带有 id 列的实体 Person,那么序列名称就是 person_seq

要修改命名策略,需要在 application.properties 中为不同的命名策略添加必要的配置。

# 使用标准命名策略
spring.jpa.properties.hibernate.id.db_structure_naming_strategy=standard

下面介绍如何为每种命名策略设置配置。

来看一个基本的 Person 实体类:

@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    private String name;

    // Getter / Setter 省略
}

在本例中,由于我们使用的是标准策略,因此在建表的同时,Hibernate 会自动生成一个名为 person_seq 的序列:

Hibernate: 
  create table person (
    id bigint not null,
    name varchar(255),
    primary key (id)
  )
Hibernate: 
  create sequence person_seq start with 1 increment by 50

标准策略的一个关键点是其默认增量(increment)值。Hibernate 会分配一个较大的值,如 50,以优化批处理操作,减少序列检索所需的数据库调用次数。

当我们插入一条 Person 记录时,Hibernate 会使用 person_seq 序列来生成主键:

Hibernate: 
  select next value for person_seq
Hibernate: 
  insert 
    into person (name,id) 
    values (?,?)

此外,我们还可以使用 @Table 注解来覆盖表映射。这样,就可以指定一个自定义表名,然后使用该表名生成相应的序列名。

例如,如果我们有一个带有 id 列的实体 Person,我们可以指定一个自定义表名 my_person_table,以生名为 my_person_table _seq 的序列:

@Entity
@Table(name = "my_person_table")
public class Person {
    // ...
}

在本例中,表名为 my_person_table,生成的序列名为 my_person_table_seq

Hibernate: 
  create table my_person_table (
    id bigint not null,
    name varchar(255),
    primary key (id)
)
Hibernate: 
  create sequence my_person_table_seq start with 1 increment by 50

当我们尝试插入一条 Person 记录时,Hibernate 会利用 my_person_table_seq 生成主键值:

Hibernate: 
  select
    next value for my_person_table_seq

3、传统命名策略

该策略与标准策略类似,但它使用传统的命名约定或生成器名称(如果指定)来生成序列名称。例如,我们有一个带有列 id 的实体 Person,那么序列名称就是 hibernate_sequence。而且,hibernate_sequence 序列会在所有实体中使用。

要启用传统策略,要在 application.properties 文件中将 hibernate.id.db_structure_naming_strategy 属性设置为 legacy

spring.jpa.properties.hibernate.id.db_structure_naming_strategy=legacy

来看看使用传统策略的现有 Person 实体类。在这种情况下,Hibernate 会创建表和一个名为 hibernate_sequence 的单个序列:

Hibernate: 
  create table person (
    id bigint not null,
    name varchar(255),
    primary key (id)
  )
Hibernate: 
  create sequence hibernate_sequence start with 1 increment by 1

与标准命名策略不同,使用传统命名策略时,序列的默认增量值通常为 1。 这意味着序列产生的每个新值都将以 1 为增量。

在插入 Person 记录时,Hibernate 依赖 hibernate_sequence 来生成主键:

Hibernate: 
  select next value for hibernate_sequence
Hibernate: 
  insert 
    into person (name,id) 
    values (?,?)

即使使用传统策略,我们也可以使用 @Table 注解自定义表名。在这种情况下,生成的序列名称不会反映表的名称。

不过,我们可以使用 @SequenceGenerator 注解来指定自定义序列名称。这对于管理特定实体的序列特别有用:

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

如下,使用 @SequenceGenerator 注解指定了一个分配大小为 10 的自定义序列名称 person_custom_seq@GeneratedValue 注解被设置为使用该序列生成器:

Hibernate: 
  create table my_person_table (
    id bigint not null,
    name varchar(255),
    primary key (id)
  )
Hibernate: 
  create sequence person_custom_seq start with 1 increment by 10

当插入一条 Person 记录时,Hibernate 会执行以下 SQL 语句,从 person_custom_seq 序列中检索下一个可用值:

Hibernate: 
  select next value for person_custom_seq

传统策略主要是为了保持与旧版本 Hibernate 的兼容性,避免破坏现有系统。

4、唯一命名策略

Hibernate 6 引入了唯一命名策略,通过为同一 Schema 中的所有实体使用统一的序列名称来简化序列命名。与传统策略类似,在使用唯一命名策略时,Hibernate 会生成一个名为 hibernate_sequence 的唯一序列,并在模式中的所有实体间共享,这对于确保序列管理方法的一致性特别有用。

来看看这在 PersonBook 这两个不同的实体中是如何使用的。

要使用唯一命名策略,需要在 application.properties 文件中进行配置:

spring.jpa.properties.hibernate.id.db_structure_naming_strategy=single

如下,使用唯一命名策略创建两个实体:PersonBook

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    private String title;

    // Getter / Setter 省略
}

使用唯一命名策略时,Hibernate 会为所有实体生成唯一的一个序列。下面是实体的 SQL 语句:

Hibernate: 
  create table book (
    id bigint not null,
    title varchar(255),
    primary key (id)
  )
Hibernate: 
  create table person (
    id bigint not null,
    name varchar(255),
    primary key (id)
  )
Hibernate: 
  create sequence hibernate_sequence start with 1 increment by 1

你可以看到,在这里,我们只创建了一个序列 hibernate_sequence。因此,在向 PersonBook 表插入记录时,Hibernate 会使用 hibernate_sequence 序列来生成主键值:

Person person = new Person();
person.setName("John Doe");
personRepository.save(person);

Book book = new Book();
book.setTitle("Baeldung");
bookRepository.save(book);

List<Person> personList = personRepository.findAll();
List<Book> bookList = bookRepository.findAll();

assertEquals((long)1,(long) personList.get(0).getId());
assertEquals((long)2, (long) bookList.get(0).getId());

如上,我们创建并保存两个实体:PersonBook。这两个实体应使用相同的序列生成器生成主键。当我们先保存 Person,再保存 Book 时,Person 的 ID 应该是 1Book 的 ID 应该是 2

5、自定义命名策略

此外,我们还可以通过自定义命名策略来定义自己的序列命名约定。要为序列使用自定义命名策略,我们首先需要创建 ImplicitDatabaseObjectNamingStrategy 的自定义实现。该接口用于为包括序列在内的各种数据库对象提供自定义命名策略。

如下,创建 ImplicitDatabaseObjectNamingStrategy 的自定义实现,自定义序列名称:

public class CustomSequenceNamingStrategy implements ImplicitDatabaseObjectNamingStrategy {
    @Override
    public QualifiedName determineSequenceName(Identifier catalogName, Identifier schemaName, Map<?, ?> map, ServiceRegistry serviceRegistry) {
        JdbcEnvironment jdbcEnvironment = serviceRegistry.getService(JdbcEnvironment.class);
        String seqName = ((String) map.get("jpa_entity_name")).concat("_custom_seq");
        return new QualifiedSequenceName(
          catalogName,
          schemaName,
          jdbcEnvironment.getIdentifierHelper().toIdentifier(seqName));
    }

    // 其他方法
}

determineSequenceName() 方法中,我们自定义了序列名称的生成。首先,使用 JdbcEnvironment 服务访问各种与数据库相关的设置,这有助于我们高效地管理数据库对象名称。然后,在实体名称后添加 _custom_seq 后缀,创建自定义序列名称。通过这种方法,我们可以控制整个数据库 Schema 的序列名称格式。

接下来,需要配置 Hibernate 以使用自定义命名策略:

spring.jpa.properties.hibernate.id.db_structure_naming_strategy=com.baeldung.sequencenaming.CustomSequenceNamingStrategy

通过自定义的 ImplicitDatabaseObjectNamingStrategy,Hibernate 将生成包含我们定义的自定义序列名称的 SQL 语句:

Hibernate: 
  create table person (
    id bigint not null,
    name varchar(255),
    primary key (id)
  )
Hibernate: 
  create sequence person_custom_seq start with 1 increment by 50

除了 ImplicitDatabaseObjectNamingStrategy 之外,我们还可以使用 PhysicalNamingStrategy,它定义了所有数据库对象的综合命名约定。使用 PhysicalNamingStrategy,我们可以实现复杂的命名规则,这些规则不仅适用于序列,也适用于其他数据库对象。

下面是一个如何使用 PhysicalNamingStrategy 实现自定义序列命名的示例:

public class CustomPhysicalNamingStrategy extends DelegatingPhysicalNamingStrategy {
    @Override
    public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment context) {
        return new Identifier(name.getText() + "_custom_seq", name.isQuoted());
    }

    // 针对表和列的其他方法
}

6、总结

本文介绍了如何在 Hibernate 6 中使用自定义命名策略。标准策略根据实体名称生成序列名称,而传统策略和唯一策略则通过使用统一的序列名称来简化序列管理。此外,自定义命名策略还允许我们定制序列命名约定。


Ref:https://www.baeldung.com/hibernate-sequence-naming-strategies