documentation improvements on top of Vlad's work - naming
This commit is contained in:
parent
d769308785
commit
92bce3913b
|
@ -59,6 +59,7 @@ dependencies {
|
|||
|
||||
testCompile( project(':hibernate-testing') )
|
||||
testCompile( project(path: ':hibernate-entitymanager', configuration: 'tests') )
|
||||
testCompile( 'org.apache.commons:commons-lang3:3.4' )
|
||||
testRuntime( libraries.h2 )
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,16 @@
|
|||
public class Contact {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private Name name;
|
||||
|
||||
private String notes;
|
||||
|
||||
private URL website;
|
||||
|
||||
private boolean starred;
|
||||
|
||||
// getters and setters ommitted
|
||||
}
|
||||
|
||||
public class Name {
|
||||
|
||||
private String first;
|
||||
|
||||
private String middle;
|
||||
|
||||
private String last;
|
||||
|
||||
// getters and setters ommitted
|
||||
|
|
|
@ -1,52 +1,129 @@
|
|||
[[naming]]
|
||||
=== Naming strategies
|
||||
:sourcedir: extras
|
||||
:sourcedir: ../../../../../test/java/org/hibernate/jpa/test/userguide/naming
|
||||
|
||||
Part of the mapping of an object model to the relational database is
|
||||
mapping names from the object model to the corresponding database names.
|
||||
Hibernate looks at this as 2 stage process:
|
||||
|
||||
* The first stage is determining a proper logical name from the domain model mapping. A
|
||||
logical name can be either explicitly specified by the user (using `@Column` or
|
||||
`@Table` e.g.) or it can be implicitly determined by Hibernate through an
|
||||
<<ImplicitNamingStrategy>> contract.
|
||||
* Second is the resolving of this logical name to a physical name which is defined
|
||||
by the <<PhysicalNamingStrategy>> contract.
|
||||
|
||||
The naming strategy provide rules for automatically generating database identifiers from Java identifiers or for processing "logical" column and table names given in the mapping file into "physical" table and column names.
|
||||
This feature helps reduce the verbosity of the mapping document, eliminating repetitive noise (TBL_ prefixes, for example).
|
||||
|
||||
[NOTE]
|
||||
.Historical NamingStrategy contract
|
||||
====
|
||||
Traditionally, Hibernate defined the `org.hibernate.cfg.NamingStrategy` abstraction for mapping domain model names to their database equivalents.
|
||||
Previously, the naming strategy could be customized by implementing this interface and provide the alternative implementation via the `hibernate.ejb.naming_strategy` configuration property.
|
||||
Historically Hibernate defined just a single `org.hibernate.cfg.NamingStrategy`. That singular
|
||||
NamingStrategy contract actually combined the separate concerns that are now modeled individually
|
||||
as ImplicitNamingStrategy and PhysicalNamingStrategy.
|
||||
|
||||
When annotations or JPA XML descriptors are used to map an entity, the `org.hibernate.cfg.NamingStrategy` API may not be flexible enough to properly generate default collection table or join column names that comply with the JPA specification.
|
||||
This is because the API does not provide all the necessary information (e.g., an entity's class name, along with its mapped name and primary table name) to compute the names properly.
|
||||
Due to this limitation, `org.hibernate.cfg.NamingStrategy` has been deprecated.
|
||||
Also the NamingStrategy contract was often not flexible enough to properly apply a given naming
|
||||
"rule", either because the API lacked the information to decide or because the API was honestly
|
||||
not well defined as it grew.
|
||||
|
||||
Due to these limitation, `org.hibernate.cfg.NamingStrategy` has been deprecated and then removed
|
||||
in favor of ImplicitNamingStrategy and PhysicalNamingStrategy.
|
||||
====
|
||||
|
||||
Nowadays, the `org.hibernate.cfg.NamingStrategy` is replaced by two newer abstractions:
|
||||
At the core, the idea behind each naming strategy is to minimize the amount of
|
||||
repetitive information a developer must provider for mapping a domain model.
|
||||
|
||||
`org.hibernate.boot.model.naming.PhysicalNamingStrategy`:: This strategy is used to decorate database object names based on the current JDBC environment settings
|
||||
`org.hibernate.boot.model.naming.ImplicitNamingStrategy`:: It defines how the implicit entity-related annotations should be translated to their database counterparts.
|
||||
|
||||
The default implementation of the `PhysicalNamingStrategy` interface simply passes the database object names without any decoration.
|
||||
|
||||
[NOTE]
|
||||
.JPA Compatibility
|
||||
====
|
||||
To overrule the default `PhysicalNamingStrategy` and provide a custom implementation,
|
||||
you have to set the `hibernate.physical_naming_strategy` configuration property with the fully-qualified class name of the custom `PhysicalNamingStrategy` implementation.
|
||||
JPA defines inherent rules about implicit logical name determination. If JPA provider
|
||||
portability is a major concern, or if you really just like the JPA defined implicit
|
||||
naming rules, be sure to stick with ImplicitNamingStrategyJpaCompliantImpl (the default)
|
||||
|
||||
Also, JPA defines no separation between logical and physical name. Following the JPA
|
||||
specification, the logical name *is* the physical name. If JPA provider portability
|
||||
is important, applications should prefer to not specify a PhysicalNamingStrategy.
|
||||
====
|
||||
|
||||
This implicit naming strategy is JPA compliant and Hibernate 5.0 offers additional implementations that are backward compatible with previous versions of Hibernate.
|
||||
|
||||
[[ImplicitNamingStrategy]]
|
||||
==== ImplicitNamingStrategy
|
||||
|
||||
When an entity does not explicitly name the database table that it maps to, we need
|
||||
to implicitly determine that table name. Or when a particular attribute does not explicitly name
|
||||
the database column that it maps to, we need to implicitly determine that column name. There are
|
||||
examples of the role of the `org.hibernate.boot.model.naming.ImplicitNamingStrategy` contract : to
|
||||
determine a logical name when the mapping did not provide an explicit name.
|
||||
|
||||
image:images/domain/naming/implicit_naming_strategy_diagram.svg[Implicit Naming Strategy Diagram]
|
||||
|
||||
Hibernate defines multiple ImplicitNamingStrategy implementations out-of-the-box. Applications
|
||||
are also free to plug-in custom implementations.
|
||||
|
||||
There are multiple ways to specify the ImplicitNamingStrategy to use. First, applications can specify
|
||||
the implementation using the `hibernate.implicit_naming_strategy` configuration setting which accepts:
|
||||
|
||||
* pre-defined "short names" for the out-of-the-box implementations
|
||||
+
|
||||
`default`:: for `org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl` - an alias for `jpa`
|
||||
`jpa`:: for `org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl` - the JPA 2.0 compliant naming strategy
|
||||
`legacy-hbm`:: for `org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl` - compliant with the original Hibernate NamingStrategy
|
||||
`legacy-jpa`:: for `org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl` - compliant with the legacy NamingStrategy developed for JPA 1.0, which was unfortunately unclear in many respects regarding implicit naming rules.
|
||||
`component-path`:: for `org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl` - mostly follows `ImplicitNamingStrategyJpaCompliantImpl` rules, except that it uses the full composite paths, as opposed to just the ending property part
|
||||
+
|
||||
* reference to a Class that implements the the `org.hibernate.boot.model.naming.ImplicitNamingStrategy` contract
|
||||
* FQN of a class that implements the the `org.hibernate.boot.model.naming.ImplicitNamingStrategy` contract
|
||||
|
||||
Secondly applications and integrations can leverage `org.hibernate.boot.MetadataBuilder#applyImplicitNamingStrategy`
|
||||
to specify the ImplicitNamingStrategy to use. See
|
||||
<<chapters/bootstrap/Bootstrap.adoc#Bootstrap,Bootstrap>> for additional details on bootstrapping.
|
||||
|
||||
|
||||
|
||||
[[PhysicalNamingStrategy]]
|
||||
==== PhysicalNamingStrategy
|
||||
|
||||
Many organizations define rules around the naming of database objects (tables, columns, foreign-keys, etc).
|
||||
The idea of a PhysicalNamingStrategy is to help implement such naming rules without having to hard-code them
|
||||
into the mapping via explicit names.
|
||||
|
||||
While the purpose of an ImplicitNamingStrategy is to determine that an attribute named `accountNumber` maps to
|
||||
a logical column name of `accountNumber` when not explicitly specified, the purpose of a PhysicalNamingStrategy
|
||||
would be, for example, to say that the physical column name should instead be abbreviated `acct_num`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
You can switch to the any of these implementations by setting the `hibernate.implicit_naming_strategy` configuration properties to one of the following values:
|
||||
It is true that the resolution to `acct_num` could have been handled in an ImplicitNamingStrategy in this case.
|
||||
But the point is separation of concerns. The PhysicalNamingStrategy will be applied regardless of whether
|
||||
the attribute explicitly specified the column name or whether we determined that implicitly. The
|
||||
ImplicitNamingStrategy would only be applied if an explicit name was not given. So it depends on needs
|
||||
and intent.
|
||||
====
|
||||
|
||||
`default`:: for `org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl`
|
||||
The default implementation is to simply use the logical name as the physical name. However
|
||||
applications and integrations can define custom implementations of this PhysicalNamingStrategy
|
||||
contract. Here is an example PhysicalNamingStrategy for a fictitious company named Acme Corp
|
||||
whose naming standards are to:
|
||||
|
||||
`jpa`:: for `org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl` (the JPA 2.0 compliant naming strategy)
|
||||
* prefer underscore-delimited words rather than camel-casing
|
||||
* replace certain words with standard abbreviations
|
||||
|
||||
`legacy-jpa` for `org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl` (the original JPA 1.0 naming strategy, which was later subject to change)
|
||||
.Example PhysicalNamingStrategy implementation
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
include::{sourcedir}/AcmeCorpPhysicalNamingStrategy.java[]
|
||||
----
|
||||
====
|
||||
|
||||
`legacy-hbm` for `org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl` (the original Hibernate naming strategy behavior)
|
||||
There are multiple ways to specify the PhysicalNamingStrategy to use. First, applications can specify
|
||||
the implementation using the `hibernate.physical_naming_strategy` configuration setting which accepts:
|
||||
|
||||
* reference to a Class that implements the the `org.hibernate.boot.model.naming.PhysicalNamingStrategy` contract
|
||||
* FQN of a class that implements the the `org.hibernate.boot.model.naming.PhysicalNamingStrategy` contract
|
||||
|
||||
Secondly applications and integrations can leverage `org.hibernate.boot.MetadataBuilder#applyPhysicalNamingStrategy`.
|
||||
See <<chapters/bootstrap/Bootstrap.adoc#Bootstrap,Bootstrap>> for additional details on bootstrapping.
|
||||
|
||||
`component-path` for `org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl` (A JPA compliant strategy that uses the full composite paths, as opposed to just the ending property part)
|
||||
|
||||
By default, Hibernate 5.0 chooses the `default` value, which designates the `ImplicitNamingStrategyJpaCompliantImpl` strategy.
|
||||
|
||||
To provide a custom implementation, you have to set the `hibernate.implicit_naming_strategy` configuration property with the fully-qualified class name of the custom `PhysicalNamingStrategy` implementation.
|
||||
====
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.jpa.test.userguide.naming;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.hibernate.boot.model.naming.Identifier;
|
||||
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
|
||||
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* An example PhysicalNamingStrategy that implements database object naming standards
|
||||
* for our fictitious company Acme Corp.
|
||||
* <p/>
|
||||
* In general Acme Corp prefers underscore-delimited words rather than camel casing.
|
||||
* <p/>
|
||||
* Additionally standards call for the replacement of certain words with abbreviations.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class AcmeCorpPhysicalNamingStrategy implements PhysicalNamingStrategy {
|
||||
private static final Map<String,String> ABBREVIATIONS = buildAbbreviationMap();
|
||||
|
||||
@Override
|
||||
public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment jdbcEnvironment) {
|
||||
// Acme naming standards do not apply to catalog names
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment jdbcEnvironment) {
|
||||
// Acme naming standards do not apply to schema names
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) {
|
||||
final List<String> parts = splitAndReplace( name.getText() );
|
||||
return jdbcEnvironment.getIdentifierHelper().toIdentifier(
|
||||
join( parts ),
|
||||
name.isQuoted()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment jdbcEnvironment) {
|
||||
final LinkedList<String> parts = splitAndReplace( name.getText() );
|
||||
// Acme Corp says all sequences should end with _seq
|
||||
if ( !"seq".equalsIgnoreCase( parts.getLast() ) ) {
|
||||
parts.add( "seq" );
|
||||
}
|
||||
return jdbcEnvironment.getIdentifierHelper().toIdentifier(
|
||||
join( parts ),
|
||||
name.isQuoted()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment) {
|
||||
final List<String> parts = splitAndReplace( name.getText() );
|
||||
return jdbcEnvironment.getIdentifierHelper().toIdentifier(
|
||||
join( parts ),
|
||||
name.isQuoted()
|
||||
);
|
||||
}
|
||||
|
||||
private static Map<String, String> buildAbbreviationMap() {
|
||||
TreeMap<String,String> abbreviationMap = new TreeMap<> ( String.CASE_INSENSITIVE_ORDER );
|
||||
abbreviationMap.put( "account", "acct" );
|
||||
abbreviationMap.put( "number", "num" );
|
||||
return abbreviationMap;
|
||||
}
|
||||
|
||||
private LinkedList<String> splitAndReplace(String name) {
|
||||
LinkedList<String> result = new LinkedList<>();
|
||||
for ( String part : StringUtils.splitByCharacterTypeCamelCase( name ) ) {
|
||||
if ( part == null || part.trim().isEmpty() ) {
|
||||
// skip null and space
|
||||
continue;
|
||||
}
|
||||
part = applyAbbreviationReplacement( part );
|
||||
result.add( part.toLowerCase( Locale.ROOT ) );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String applyAbbreviationReplacement(String word) {
|
||||
if ( ABBREVIATIONS.containsKey( word ) ) {
|
||||
return ABBREVIATIONS.get( word );
|
||||
}
|
||||
|
||||
return word;
|
||||
}
|
||||
|
||||
private String join(List<String> parts) {
|
||||
boolean firstPass = true;
|
||||
String separator = "";
|
||||
StringBuilder joined = new StringBuilder();
|
||||
for ( String part : parts ) {
|
||||
joined.append( separator ).append( part );
|
||||
if ( firstPass ) {
|
||||
firstPass = false;
|
||||
separator = "_";
|
||||
}
|
||||
}
|
||||
return joined.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.jpa.test.userguide.naming;
|
||||
|
||||
import org.hibernate.boot.model.naming.Identifier;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class AcmeCorpPhysicalNamingStrategyTest {
|
||||
private AcmeCorpPhysicalNamingStrategy strategy = new AcmeCorpPhysicalNamingStrategy();
|
||||
private StandardServiceRegistry serviceRegistry;
|
||||
|
||||
@Before
|
||||
public void prepareServiceRegistry() {
|
||||
serviceRegistry = new StandardServiceRegistryBuilder().build();
|
||||
}
|
||||
|
||||
@After
|
||||
public void releaseServiceRegistry() {
|
||||
if ( serviceRegistry != null ) {
|
||||
StandardServiceRegistryBuilder.destroy( serviceRegistry );
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTableNaming() {
|
||||
{
|
||||
Identifier in = Identifier.toIdentifier( "accountNumber" );
|
||||
Identifier out = strategy.toPhysicalTableName( in, serviceRegistry.getService( JdbcEnvironment.class ) );
|
||||
assertThat( out.getText(), equalTo( "acct_num" ) );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue