1. 程式人生 > >Hibernate入門之命名策略(naming strategy)詳解

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探索之旅。&n