Hibernate 6引入了一个新的配置参数和一个接口,用于定义用于生成主键值的数据库序列和表的隐式命名策略。当你将一个现有的应用程序迁移到Hibernate 6时,你很快就会认识到这种变化,因为默认的命名策略已经改变了。由于这个原因,Hibernate可能会尝试使用一个在你的数据库中不存在的序列。但这并不是你应该熟悉这个新设置的唯一情况。它也可以帮助映射一个具有奇怪命名序列的传统数据库,或者如果你需要遵循一些内部命名惯例。
从Hibernate 6开始,你可以使用配置属性hibernate.id.db_structure_naming_strategy来定义Hibernate应使用的命名策略,如果你没有在映射中明确定义序列名称。
<persistence>
<persistence-unit name="my-persistence-unit">
...
<properties>
<property name="hibernate.id.db_structure_naming_strategy" value="standard" />
...
</properties>
</persistence-unit>
</persistence>
内容
- 1Hibernate 6支持的命名策略
- 1.1ID_DB_STRUCTURE_NAMING_STRATEGY= 标准
- 1.2ID_DB_STRUCTURE_NAMING_STRATEGY = legacy
- 1.3ID_DB_STRUCTURE_NAMING_STRATEGY = single
- 1.4ID_DB_STRUCTURE_NAMING_STRATEGY =自定义类
- 2修复Hibernate 6中的迁移问题
- 3结论
Hibernate 6支持的命名策略
以前的Hibernate版本提供了1种默认行为,如果你想使用不同的序列名称,你必须指定序列名称。在Hibernate 6中,你可以为数据库序列选择4种隐式命名策略。
-
标准
这是在Hibernate 6中新的默认值。
它将配置的序列后缀(默认为*_SEQ*)与实体类映射的表的名称相连接。 -
遗产
这种命名策略为你提供了与Hibernate版本>=5.3但默认使用的<6版本相同的行为。
序列名称取决于你的实体映射定义:-
如果你引用了一个生成器而没有定义序列名称,Hibernate会使用生成器的名称。如果你只想定义序列名,这简化了映射,是Hibernate 5.3中引入的优化 。 如果你的映射没有引用生成器,Hibernate会使用其默认的序列名hibernate_sequence 。
-
-
单一的
这种命名策略为你提供了与Hibernate在<5.3版本中默认使用的相同行为。
它总是使用Hibernate的默认序列名hibernate_sequence。 -
ImplicitDatabaseObjectNamingStrategy 实现的完全合格的类名,
这使你可以提供你自己的命名策略。我将在本文的最后向你展示如何做到这一点。
让我们仔细看看所有4种命名策略。
ID_DB_STRUCTURE_NAMING_STRATEGY= 标准
与之前的Hibernate版本相比,Hibernate 6默认为每个实体类使用一个单独的数据库序列。该序列的名称由实体类被映射到的数据库表的名称和后缀_SEQ组成。
隐式表的映射
如果你没有指定数据库表的名称,Hibernate会使用其隐式命名策略。默认策略使用实体类的简单类名作为表名。
@Entity
public class ChessPlayer {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String firstName;
private String lastName;
private LocalDate birthDate;
@Version
private int version;
...
}
所以,ChessPlayer实体类会被映射到ChessPlayer 表。而如果你使用Hibernate 6的数据库序列标准命名策略,Hibernate会使用序列ChessPlayer_SEQ 来生成主键值:
ChessPlayer player = new ChessPlayer();
player.setFirstName("Thorben");
player.setLastName("Janssen");
em.persist(player);
16:15:04,917 DEBUG [org.hibernate.SQL] -
select
nextval('ChessPlayer_SEQ')
16:15:04,947 DEBUG [org.hibernate.SQL] -
insert
into
ChessPlayer
(birthDate, firstName, lastName, version, id)
values
(?, ?, ?, ?, ?)
自定义表的映射
你可以通过给你的实体类加上*@Table*注解并设置数据库表的名称来定制表的映射:
@Entity
@Table(name="player")
public class ChessPlayer {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
...
}
让我们用前面的测试案例来使用这种映射。你可以在日志输出中看到,Hibernate现在调用数据库序列player_SEQ来生成主键值。它还将ChessPlayer实体对象持久化到player表中。
16:17:04,094 DEBUG [org.hibernate.SQL] -
select
nextval('player_SEQ')
16:17:04,120 DEBUG [org.hibernate.SQL] -
insert
into
player
(birthDate, firstName, lastName, version, id)
values
(?, ?, ?, ?, ?)
ID_DB_STRUCTURE_NAMING_STRATEGY=legacy
传统的命名 策略 可以得到与Hibernate在>=5.3和<6版本中使用的相同策略。你可以通过将persistence.xml配置中的属性hibernate.id.db_structure_naming_strategy 设置为legacy 来激活它。
<persistence>
<persistence-unit name="my-persistence-unit">
...
<properties>
<property name="hibernate.id.db_structure_naming_strategy" value="legacy" />
...
</properties>
</persistence-unit>
</persistence>
这个命名策略的行为取决于你的实体映射。
没有生成器引用的映射
Hibernate对所有用*@GeneratedValue或@GeneratedValue(strategy = GenerationType.SEQUENCE)*注释的主键属性使用1个默认序列。这2个映射的重要之处在于,它们没有引用生成器。
@Entity
public class ChessPlayer {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String firstName;
private String lastName;
private LocalDate birthDate;
@Version
private int version;
...
}
当你使用传统的命名策略持久化这个ChessPlayer实体时,Hibernate使用数据库序列hibernate_sequence来生成主键值。
16:51:10,742 DEBUG [org.hibernate.SQL] -
select
nextval('hibernate_sequence')
16:51:10,771 DEBUG [org.hibernate.SQL] -
insert
into
ChessPlayer
(birthDate, firstName, lastName, version, id)
values
(?, ?, ?, ?, ?)
有生成器引用但没有序列名称的映射
如果你的主键映射引用了一个不存在的生成器,或者没有定义sequenceName,Hibernate会使用生成器的名称作为序列名称。这个Hibernate特有的优化是在5.3版本中引入的,以简化最常用的映射定义,它只定制了数据库序列的名称。
@Entity
public class ChessPlayer {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="player_seq")
private Long id;
private String firstName;
private String lastName;
private LocalDate birthDate;
@Version
private int version;
...
}
当你持久化这个实体类的一个对象时,Hibernate使用数据库序列player_seq 来生成主键值。
16:51:50,304 DEBUG [org.hibernate.SQL] -
select
nextval('player_seq')
16:51:50,343 DEBUG [org.hibernate.SQL] -
insert
into
ChessPlayer
(birthDate, firstName, lastName, version, id)
values
(?, ?, ?, ?, ?)
ID_DB_STRUCTURE_NAMING_STRATEGY = single
命名策略single 是传统 策略的一个简单版本,可以得到Hibernate在<5.3版本中的默认命名。你可以通过将persistence.xml 配置中的属性hibernate.id.db_structure_naming_strategy 设置为single 来激活它。
<persistence>
<persistence-unit name="my-persistence-unit">
...
<properties>
<property name="hibernate.id.db_structure_naming_strategy" value="single" />
...
</properties>
</persistence-unit>
</persistence>
如果你没有在映射定义中指定一个序列名称,这个策略总是使用数据库序列hibernate_sequence。
@Entity
public class ChessPlayer {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String firstName;
private String lastName;
private LocalDate birthDate;
@Version
private int version;
...
}
你可以在Hibernate的日志输出中看到,如果你使用命名策略single来持久化这个ChessPlayer 类的一个对象。
16:57:15,706 DEBUG [org.hibernate.SQL] -
select
nextval('hibernate_sequence')
16:57:15,734 DEBUG [org.hibernate.SQL] -
insert
into
ChessPlayer
(birthDate, firstName, lastName, version, id)
values
(?, ?, ?, ?, ?)
ID_DB_STRUCTURE_NAMING_STRATEGY= custom class
我在前面的章节中向你展示了Hibernate对数据库序列的3种标准命名策略。你可以使用同样的机制来提供你自己的命名策略。你只需要提供一个ImplicitDatabaseObjectNamingStrategy 接口的自定义实现,并在你的persistence.xml中配置它。
ImplicitDatabaseObjectNamingStrategy接口的实现不需要很复杂。该接口只定义了两个方法,它们都返回一个QualifiedName对象。
public class MyImplicitDatabaseObjectNamingStrategy implements ImplicitDatabaseObjectNamingStrategy {
public static final String STRATEGY_NAME = "custom";
@Override
public QualifiedName determineSequenceName(Identifier catalogName, Identifier schemaName, Map<?, ?> configValues,
ServiceRegistry serviceRegistry) {
final JdbcEnvironment jdbcEnvironment = serviceRegistry.getService(JdbcEnvironment.class);
String seqName = "seq_".concat(((String) configValues.get("jpa_entity_name")));
return new QualifiedSequenceName(
catalogName,
schemaName,
jdbcEnvironment.getIdentifierHelper().toIdentifier(seqName));
}
@Override
public QualifiedName determineTableName(Identifier catalogName, Identifier schemaName, Map<?, ?> configValues,
ServiceRegistry serviceRegistry) {
final JdbcEnvironment jdbcEnvironment = serviceRegistry.getService(JdbcEnvironment.class);
return new QualifiedNameParser.NameParts(
catalogName,
schemaName,
jdbcEnvironment.getIdentifierHelper().toIdentifier(DEF_TABLE));
}
}
determineSequenceName方法返回Hibernate应使用的数据库序列的名称。determineTableName方法返回Hibernate用来模拟序列的数据库表的名称。
在这篇文章中,我没有涉及到实现determineTableName 方法的任何细节。你可以用与数据库序列的名称解析相同的方式来定制它。但是模拟序列会导致很多可扩展性问题,而且所有现代数据库都支持序列或自动递增列。因此,这种机制已经不再具有实际意义。请坚持使用返回Hibernate默认表名的默认实现,并使用序列或自动递增的列来生成主键值。
determineSequenceName方法的实现完全取决于你的表模型和应用需求。Map configValues方法参数包含几个关于实体类和数据库表的映射信息,你可以用它来生成你的序列名。在这个例子中,我实现了一个简单的命名策略,使用seq_作为所有序列名称的前缀,并将其与实体类的逻辑名称连接起来。
实体类的逻辑名称是你的实体类的简单类名或你在*@Entity*注解中定义的名称。
@Entity(name="Player")
public class ChessPlayer {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String firstName;
private String lastName;
private LocalDate birthDate;
@Version
private int version;
...
}
实现了ImplicitDatabaseObjectNamingStrategy接口后,你需要在你的配置中引用它。你可以通过将配置属性hibernate.id.db_structure_naming_strategy设置为你的接口实现的全限定类名来实现。
<persistence>
<persistence-unit name="my-persistence-unit">
...
<properties>
<property name="hibernate.id.db_structure_naming_strategy" value="com.thorben.janssen.sample.model.MyImplicitDatabaseObjectNamingStrategy" />
...
</properties>
</persistence-unit>
</persistence>
当你使用与前面例子中相同的测试案例来持久化一个ChessPlayer实体对象时,你可以看到Hibernate现在使用数据库序列seq_Player 来生成主键值。
17:06:51,325 DEBUG [org.hibernate.SQL] -
select
nextval('seq_Player')
17:06:51,352 DEBUG [org.hibernate.SQL] -
insert
into
Player
(birthDate, firstName, lastName, version, id)
values
(?, ?, ?, ?, ?)
修复Hibernate 6中的迁移问题
当你将现有的应用程序迁移到Hibernate 6时,默认的命名策略会从单一(如果你一直在使用Hibernate <5.3)或传统(如果你一直在使用Hibernate >=5.3)变为标准。正如我前面所描述的,这改变了Hibernate用来生成主键值的序列的名称。
如果你遇到了这个问题,你可以通过在映射中明确定义序列名称,迁移你的数据库模式,或者在你的 persistence.xml中配置旧的命名策略来解决它。
<persistence>
<persistence-unit name="my-persistence-unit">
...
<properties>
<property name="hibernate.id.db_structure_naming_strategy" value="single" />
...
</properties>
</persistence-unit>
</persistence>
结论
ImplicitDatabaseObjectNamingStrategy接口和配置属性hibernate.id.db_structure_naming_strategy在Hibernate 6中引入了一种新的隐式命名策略。它定义了如果你没有在实体映射定义中指定它们的名字,Hibernate如何确定数据库序列或用于模拟序列的数据库表的名称。
大多数开发者在将他们的应用程序迁移到Hibernate 6时需要使用这个配置,因为Hibernate的默认隐式命名策略已经改变。Hibernate现在不再对所有没有指定序列的实体类使用1个默认序列,而是生成了一个实体特定的默认名称。你可以通过设置配置属性hibernate.id.db_structure_naming_strategy来告诉Hibernate使用旧的命名策略,如果你是从Hibernate版本<5.3迁移过来的,可以设置为single;如果你是从Hibernate版本>=5.3迁移过来的,可以设置为legacy 。
你也可以为数据库序列提供你自己的命名策略。要做到这一点,你需要实现ImplicitDatabaseObjectNamingStrategy接口,并提供完全限定的类名称作为配置属性hibernate.id.db_structure_naming_strategy的值。