Hibernate中實體對映時的命名策略(2)
在Hibernate 5.x中,Hibernate將實體名稱對映到資料庫中時,將這個過程分成兩個步驟:
- 第一個階段是從物件模型中提取一個合適的邏輯名稱,這個邏輯名稱可以由使用者指定,通過@Column和@Table等註解完成,也可以通過被Hibernate的
ImplicitNamingStrategy
指定; - 第二個階段是將上述的邏輯名稱解析成物理名稱,物理名稱是由Hibernate中的
PhysicalNamingStrategy
決定;
在之前,我在Hibernate中實體對映時的命名策略(1)中詳細說明了通過繼承DefaultNamingStrategy
來指定我們的自己的命名策略,但是從Hibernate 5.X開始,命名策略就被分成上述的兩個步驟,從官方的文件中的得知這樣做的目的是提高靈活性,減少構建命名策略過程中用到的重複的資訊。
但是從目前看來,我自身並沒有感覺到這種從新設計的好處,反而有點複雜。
一、ImplicitNamingStrategy
當一個實體物件沒有顯式的指明它要對映的資料庫表或者列的名稱時,在Hibernate內部就要為我們隱式處理,比如一個實體沒有在@Table中的指明表名,那麼表名隱式的被認為是實體名,或者@Entity中的提供的名稱。
這些隱式的處理都是Hibernate幫我們做的,Hibernate中定義了多個ImplicitNamingStrategy的實現,可以開箱即用。而之前的邏輯名名稱就是物理名稱這種策略只是其中一種,其他還包括:
- ImplicitNamingStrategyJpaCompliantImpl:預設的命名策略
- ImplicitNamingStrategyLegacyHbmImpl:相容Hibernate老版本中的命名規範;
- ImplicitNamingStrategyLegacyJpaImpl:相容JPA 1.0規範中的命名規範
- ImplicitNamingStrategyComponentPathImpl:大部分與ImplicitNamingStrategyJpaCompliantImpl,但是對於@Embedded等註解標誌的元件處理是通過使用attributePath完成的,因此如果我們在使用@Embedded註解的時候,如果要指定命名規範,可以直接繼承這個類來實現;
它們之間的關係如下:
那我們在哪設定這些策略以便它們起作用呢?
方法1:
在Hibernate的配置資訊設定的時候,如下:
Configuration config = new Configuration().configure();
config.setImplicitNamingStrategy(ImplicitNamingStrategyComponentPathImpl.INSTANCE);
方法二:
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().build();
MetadataSources sources = new MetadataSources(serviceRegistry);
MetadataBuilder builder = sources.getMetadataBuilder();
builder.applyImplicitNamingStrategy(ImplicitNamingStrategyComponentPathImpl.INSTANCE);
二、PhysicalNamingStrategy
在這個階段中,是根據業務需要制定自己的命名規範,通過使用PhysicalNamingStrategy可以實現這些規則,而不需要將表名和列名通過@Table和@Column等註解顯式指定。
PhysicalNamingStrategy和ImplicitNamingStrategy的區別
- 從處理的效果上來看,其實沒有什麼區別,但是從程式設計分層的角度來看,類似於MVC的分層,ImplicitNamingStrategy只管模型物件層次的處理,PhysicalNamingStrategy只管對映成真實的資料名稱的處理,但是為了達到相同的效果,比如將userName對映城資料列時,在PhysicalNamingStrategy決定對映成user_name,但是在ImplicitNamingStrategy也可以做到;
- 從處理的場景來看:
- 無論物件模型中是否顯式地指定列名或者已經被隱式決定,PhysicalNamingStrategy都會應用;
- 但是對於ImplicitNamingStrategy,僅僅只有當沒有顯式地提供名稱時才會使用,也就是說當物件模型中已經指定了@Table或者@Entity等name時,設定的ImplicitNamingStrategy並不會起作用。
它的使用設定與ImplicitNamingStrategy相同,也是通過兩種方法來完成設定的。
方法1:
在Hibernate的配置資訊設定的時候,如下:
Configuration config = new Configuration().configure();
config.setPhysicalNamingStrategy(new PhysicalNamingStrategyStandardImpl());
方法二:
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().build();
MetadataSources sources = new MetadataSources(serviceRegistry);
MetadataBuilder builder = sources.getMetadataBuilder();
builder.applyPhysicalNamingStrategy(new PhysicalNamingStrategyStandardImpl());
在Hibernate 5X 中PhysicalNamingStrategy的預設實現是用邏輯名稱代替物理名稱,但是我們可以根據實際需求來定義自己的策略,比如加上字首t_等等,或者使用分隔符”-“等等。而官方只給我們提供了一個實現:PhysicalNamingStrategyStandardImpl
,看其中的原始碼如下:
public class PhysicalNamingStrategyStandardImpl implements PhysicalNamingStrategy, Serializable {
/**
* Singleton access
*/
public static final PhysicalNamingStrategyStandardImpl INSTANCE = new PhysicalNamingStrategyStandardImpl();
@Override
public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment context) {
return name;
}
@Override
public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment context) {
return name;
}
@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
return name;
}
@Override
public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment context) {
return name;
}
@Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
return name;
}
}
預設情況下,使用就是這種策略,將ImplicitNamingStrategy傳過來的邏輯名直接作為資料庫中的物理名稱。
三、ImplicitNamingStrategy與PhysicalNamingStrategy的互動
由於涉及到資料庫對映的屬性過多,這裡只關注於資料庫表名和列名的對映過程,其他的屬性大體相同,也可按照如下分析過程進行。
ImplicitNamingStrategy中的determinePrimaryTableName()方法返回的引數輸入到PhysicalNamingStrategy的toPhysicalTableName()之中,過程如下:
在EntityBinder$EntityTableNamingStrategyHelper中的bindTable(…)方法中通過determineImplicitName呼叫determinePrimaryTableName獲取Identifier例項,然後將這個例項傳遞給toPhysicalTableName(…)
ImplicitNamingStrategy中的determineBasicColumnName()方法返回的引數輸入PhysicalNamingStrategy的toPhysicalColumnName()之中,通過Ejb3Column中的
redefineColumnName(...)
來完成這個轉換,其中關鍵原始碼如下:if ( StringHelper.isEmpty( columnName ) ) { if ( propertyName != null ) { /// HHH-6005 magic if ( propertyName.contains( ".collection&&element." ) ) { propertyName = propertyName.replace( "collection&&element.", "" ); } final AttributePath attributePath = AttributePath.parse( propertyName ); // 根據ImplicitNamingStrategy獲取Identifier例項 final Identifier implicitName = normalizer.normalizeIdentifierQuoting( implicitNamingStrategy.determineBasicColumnName( new ImplicitBasicColumnNameSource() { @Override public AttributePath getAttributePath() { return attributePath; } @Override public boolean isCollectionElement() { // if the propertyHolder is a collection, assume the // @Column refers to the element column return !propertyHolder.isComponent() && !propertyHolder.isEntity(); } @Override public MetadataBuildingContext getBuildingContext() { return context; } } ) ); // 將Identifier例項傳入PhysicalNamingStrategy final Identifier physicalName = physicalNamingStrategy.toPhysicalColumnName( implicitName, database.getJdbcEnvironment() ); mappingColumn.setName( physicalName.render( database.getDialect() ) ); } //Do nothing otherwise } else { final Identifier explicitName = database.toIdentifier( columnName ); final Identifier physicalName = physicalNamingStrategy.toPhysicalColumnName( explicitName, database.getJdbcEnvironment() ); mappingColumn.setName( physicalName.render( database.getDialect() ) ); }