前言
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探索之旅。