Hibernate入门之命名策略(naming strategy)详解
前言
JPA和Hibernate都提供了默认映射策略,通过映射将每个实体类映射到具有相同名称的数据库表,它的每个属性都映射到具有相同属性的列, 但是,在实际项目开发中可能出现与默认命名约定不匹配,也就是说我们需要更改默认值,我们应该肿么办呢?此时我们就需要详细了解Hibernate中的命名策略,本文略长,请耐心细读。
Hibernate 5命名策略(naming strategy)
首先我们对于Hibernate 4和Hibernate 5版本中命名策略的不同作一个大的概括,然后接下来以Hibernate 5中的命名策略进行详细讲解。我们知道在Hibernate 4中使用的命名策略是hibernate.ejb.naming_strategy,该策略使用的是EJB3NamingStrategy,ImprovedNamingStrategy,DefaultComponentSafeNamingStrategy和DefaultNamingStrategy来映射名称,同时EJB3NamingStrateg是默认命名策略,它提供驼峰字段和表名,在命名外键列时,它使用下划线(_)作为分隔符,例如,如果有一个名称为table1和主键为id的表,那么在table2中,外键列将被创建为table1_id,并且此EJB3NamingStrategy实现NamingStrategy接口。由于基于NamingStrategy命名策略接口所实现的上述策略还是不够灵活以至于无法正确应用命名规则,因此hibernate.ejb.naming_strategy不再适用,取而代之的是,引入了两种新策略来提供对命名策略的深度定制,它们是ImplicitNamingStrategy和PhysicalNamingStrategy,这两种策略分别对应的键是implicit_naming_strategy和physical_naming_strategy,Hibernate5仅提供了针对PhysicalNamingStrategy的一种实现即PhysicalNamingStrategyStandardImpl,但是针对ImplicitNamingStrategy策略(隐式命名策略)提供了几种实现。当我们未在实体中显式定义表名和列名时,将使用ImplicitNamingStrategy命名策略,其中,PhysicalNamingStrategy策略可用于显式定义实体和属性名称与数据库和列名称的映射规则。
基于前言的描述,若需要统一修改成公司规定的命名规则,我们当然可以为每个实体指定表名且为每个属性指定列名, 这需要在每个类上使用@Table注释,并在每个属性上使用@Column注释,我们称之为显式命名,但是对项目中大量的实体和属性执行此操作需要大量工作,为了提高效率,我们可以调整Hibernate的命名策略才是最佳方式,但是完成调整Hibernate中的命名策略之前,我们首先需要讨论下Hibernate的逻辑命名策略和物理命名策略之间的区别。
默认情况下即我们未进行任何配置的情况下,逻辑名称和物理名称是一样的,Hibernate将实体或属性名称到表或列名称的映射分为两个步骤:
【1】它首先确定实体或属性的逻辑名称。我们可以使用@Table和@Column注解显式设置逻辑名称,如果我们不这样做,则Hibernate将使用其隐式命名策略之一。
【2】它将逻辑名称映射为物理名称。默认情况下,Hibernate使用逻辑名作为物理名,但是,我们也可以使用PhysicalNamingStrategy策略,将逻辑名称映射为遵循内部命名约定的物理名称。
以上两个步骤我相信并不难理解,那么,为什么Hibernate要区分逻辑命名策略和物理命名策略,而在JPA规范却没有呢?JPA的命名策略方式行之也有效,但是我们会发现Hibernate的方法提供了更大的灵活性,通过将过程分为两个步骤,Hibernate允许我们实现转换,该转换将应用于所有属性和类。例如,如果我们的命名约定要求在所有表名称上加上“ s”,则可以在PhysicalNamingStrategy中执行此操作,然后呢,无论我们是在@Table注解中显式指定表名,还是基于实体名隐式地指定表名,都没有任何关系。在这两种情况下,Hibernate都会在表名的末尾添加“ s”。有的童鞋可能问到底究竟何为逻辑名称和物理名称呢?一言以蔽之,逻辑名称是未经任何配置情况下的名称(此时逻辑名称和物理名称相同),而物理名称则是最终存在数据库表中的表名和列名,可以通过注解指定。
显式命名策略(Explicit naming strategy)
显式命名策略非常简单,我们唯一需要做的就是用@Table注解实体类或使用@Column注解实体属性,例如如下:
@Entity @Table(name = "student") public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "Id") private int id; }
隐式命名策略(Implicit naming strategy)
如果我们未在注解中设置表名或列名,那么Hibernate将使用其隐式命名策略之一, 我们可以选择如下4种不同的命名策略,4种隐式命名策略存在于包【org.hibernate.boot.model.naming】下,在详细讲解以下4种隐式命名策略之前,我们需要用到以下4个类以及配置其关系,如下:
@Embeddable public class EmbeddableElement { @Column(name = "quotedField") private String quotedField; @Column private String regularField; }
@Entity @Table(name = "mainTable") public class MainEntity { @Id private long id; @ElementCollection private Set<EmbeddableElement> mainElements; @OneToMany(targetEntity = DependentEntity.class) Set<DependentEntity> dependentEntities; @OneToOne(targetEntity = OwnedEntity.class) OwnedEntity ownedEntity; }
@Entity @Table(name = "dependentTable") public class DependentEntity { @Id private Long id; @ManyToOne MainEntity mainEntity; @ElementCollection @CollectionTable(name = "dependentElements") Set<EmbeddableElement> dependentElements; }
@Entity(name = "owned_table") public class OwnedEntity { @Id private Long id; @ElementCollection @CollectionTable Set<EmbeddableElement> ownedElements; }
在上一节的创建Hibernate配置文件的基础上,添加对上述MainEntity、DependentEntity、OwnedEntity和EmbeddableElement对象的映射,如下:
【1】jpa(ImplicitNamingStrategyJpaCompliantImpl)
它作为隐式默认命名策略,也是符合JPA 2.0规范中定义的命名策略,该规范指出实体类的逻辑名称可以是@Entity批注中提供的名称,也可以是非限定的类名称。对于基本属性,它使用属性名称作为逻辑名称。对于元素集合的逻辑名称由拥有实体的类名称 + '_' + 引用实体的属性名称组成。对于联接表的逻辑名称由拥有实体的物理名称 + '_' + 引用实体的物理名称组成。上述最终生成的表如下:
create table mainTable (id bigint not null, ownedEntity_id bigint, primary key (id)) create table MainEntity_mainElements (MainEntity_id bigint not null, quotedField varchar(255), regularField varchar(255)) create table mainTable_dependentTable (MainEntity_id bigint not null, dependentEntities_id bigint not null, primary key (MainEntity_id, dependentEntities_id)) create table dependentTable (id bigint not null, mainEntity_id bigint, primary key (id)) create table dependentElements (DependentEntity_id bigint not null, quotedField varchar(255), regularField varchar(255)) create table owned_table (id bigint not null, primary key (id)) create table owned_table_ownedElements (owned_table_id bigint not null, quotedField varchar(255), regularField varchar(255))
我们看到上述所创建的元素集合表名为MainEntity_mainElements,此时它的逻辑名称是其拥有实体的类名(MainEntity)而不是定义的物理名称(mainTable)+ '_' + 元素集合属性名称组成。而关联表mainTable_dependentTable则由拥有实体的物理名称(注解@Table)+ '_' + 引用实体的物理名称(注解@Table)组成。同时发现一个很有意思的问题,上述创建表名有大写的字母,实际生成到数据库中表名全是小写,不知道这是什么原因所造成的,如下:
【2】legacy-hbm(ImplicitNamingStrategyLegacyHbmImpl)
它是Hibernate的原始命名策略,但它无法识别JPA的任何注解,但是我们可以使用Hibernate的专有配置文件和注解来定义列或实体名称,除此之外,它与上述JPA 2.0规范还存在一点差异,对于元素集合的逻辑名称组成相同。对于联接表由拥有实体的物理名称 + ‘_’ + 引用实体的属性名称组成(而不再是物理名称),也就是说引用实体(或者我们可称之为连接列)的逻辑名称为其属性名称。接下来我们将默认隐式策略修改为此隐式策略,注意不要将property节点放在mapping节点下面,否则会出现编译错误。
<property name="hibernate.implicit_naming_strategy">
org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl
</property>
create table mainTable (id bigint not null, ownedEntity_id bigint, primary key (id)) create table MainEntity_mainElements (MainEntity_id bigint not null, quotedField varchar(255), regularField varchar(255)) create table mainTable_dependentEntities (MainEntity_id bigint not null, dependentEntities bigint not null, primary key (MainEntity_id, dependentEntities)) create table dependentTable (id bigint not null, mainEntity_id bigint, primary key (id)) create table dependentElements (DependentEntity_id bigint not null, quotedField varchar(255), regularField varchar(255)) create table OwnedEntity (id bigint not null, primary key (id)) create table OwnedEntity_ownedElements (OwnedEntity_id bigint not null, quotedField varchar(255), regularField varchar(255))
比如上述MainEntity的引用实体(引用列)dependentEntities和ownedEntity,此时二者逻辑名称就是其属性名称,所以最终我们看到和JPA 2.0规范中对比下,生成的表名为mainTable_dependentEntities、OwnedEntity和OwnedEntity_ownedElements。
【3】legacy-jpa(ImplicitNamingStrategyLegacyJpaImpl)
该策略实现了JPA 1.0规范中定义的命名策略,它与JPA 2.0规范命名策略的主要区别在于:对于元素集合的逻辑名称由拥有实体的物理名称 + '_' + 引用实体的属性组成。对于联接表的逻辑名称由拥有方的物理表名称 + '_' + 引用实体的物理名称组成。legacy-jpa策略使用物理名称而不是关联引用的实体名称。
create table mainTable (id bigint not null, ownedEntity_id bigint, primary key (id)) create table mainTable_mainElements (mainTable_id bigint not null, quotedField varchar(255), regularField varchar(255)) create table mainTable_dependentTable (mainTable_id bigint not null, dependentEntities_id bigint not null, primary key (mainTable_id, dependentEntities_id)) create table dependentTable (id bigint not null, mainEntity_id bigint, primary key (id)) create table dependentElements (DependentEntity_id bigint not null, quotedField varchar(255), regularField varchar(255)) create table owned_table (id bigint not null, primary key (id)) create table owned_table_ownedElements (owned_table_id bigint not null, quotedField varchar(255), regularField varchar(255))
【4】component-path(ImplicitNamingStrategyComponentPathImpl)
该策略几乎与JPA 2.0规范中定义的命名策略相同,唯一的区别在于:它在逻辑属性名称中包含了复合名称。
create table mainTable (id bigint not null, ownedEntity_id bigint, primary key (id)) create table MainEntity_mainElements (MainEntity_id bigint not null, quotedField varchar(255), mainElements_regularField varchar(255)) create table mainTable_dependentTable (MainEntity_id bigint not null, dependentEntities_id bigint not null, primary key (MainEntity_id, dependentEntities_id)) create table dependentTable (id bigint not null, mainEntity_id bigint, primary key (id)) create table dependentElements (DependentEntity_id bigint not null, quotedField varchar(255), regularField varchar(255)) create table owned_table (id bigint not null, primary key (id)) create table owned_table_ownedElements (owned_table_id bigint not null, quotedField varchar(255), regularField varchar(255))
我们看到针对元素集合EmbeddableElement,针对regularField属性并未显式配置映射列名,此时将使用拥有实体所引用实体的(集合属性名称 + '_' + 属性名称 )作为复合名称。
物理命名策略(PhysicalNamingStrategy)
上述是对Hibernate 5中对于4种隐式命名策略的详细介绍,最好都以默认隐式命名策略作为对比,这样能更好的理解,当然我们也可以实现自定义的隐式命名策略,只需继承自上述4种隐式命名策略之一即可。回到本文开头,我们实现自定义的物理命名策略并不复杂,我们可以实现PhysicalNamingStrategy接口,也可以扩展Hibernate的PhysicalNamingStrategyStandardImpl类,通过扩展Hibernate的PhysicalNamingStrategyStandardImpl更加简单。 在以下示例中,创建一个针对每个表都添加's'即复数的自定义物理命名策略,如下:
public class TableSuffixPhysicalNamingStrategy extends PhysicalNamingStrategyStandardImpl { private final static String suffix = "s"; @Override public Identifier toPhysicalTableName(final Identifier identifier, final JdbcEnvironment jdbcEnv) { if (identifier == null) { return null; } final String newName = identifier.getText() + suffix; return Identifier.toIdentifier(newName); } }
然后在Hibernate配置文件中添加对物理命名策略的自定义实现,如下:
<property name="hibernate.physical_naming_strategy"> strategy.TableSuffixPhysicalNamingStrategy </property>
或者实现PhysicalNamingStrategy物理命名策略接口,可以达到上述同样的效果:
public class CustomPhysicalNamingStrategy implements PhysicalNamingStrategy { private final static String suffix = "s"; @Override public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment jdbcEnvironment) { return name; } @Override public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment jdbcEnvironment) { return name; } @Override public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) { return jdbcEnvironment.getIdentifierHelper().toIdentifier( name.getText() + suffix, name.isQuoted() ); } @Override public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment jdbcEnvironment) { return name; } @Override public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment) { return name; } }
总结
本节我们回顾了Hibernate 4中存在的命名策略以及在Hibernate 5种重新引入两种命名策略以解决在Hibernate 4中的不灵活性,从而我们更方便的实现自定义深度定制的命名策略,无论是隐式命名策略还是物理命名策略根据相应需求皆可,好了,本节我们到此结束,下节我们继续Hibernate 5探索之旅。