对象模型到关系数据库的映射的一部分是将对象模型中的名称映射到相应的数据库名称。

首先说明一点,Hibernate5中不再支持​​hibernate.ejb.naming_strategy​​属性配置,可以使用如下两个属性配置替换:

hibernate.implicit_naming_strategy
hibernate.physical_naming_strategy

Hibernate5以前默认命名策略接口类示意图:
Hibernate5中实体映射命名策略_spring

Hibernate5将此视为2阶段过程:

  • 第一阶段是从域模型映射中确定正确的逻辑名称。逻辑名可以由用户显式指定(使用@Column或 @Table例如),也可以由Hibernate通过ImplicitNamingStrategy契约隐式确定 。
  • 其次是将此逻辑名解析为PhysicalNamingStrategy合约定义的物理名称。

【1】 ImplicitNamingStrategy

当实体没有显式命名它映射到的数据库表时,我们需要隐式确定该表名。或者,当某个特定属性没有显式命名它映射到的数据库列时,我们需要隐式确定该列名。org.hibernate.boot.model.naming.ImplicitNamingStrategy当映射未提供显式名称时,有一些规则用于确定逻辑名称的示例。

Hibernate5中实体映射命名策略_spring_02

① ImplicitNamingStrategy的实现

Hibernate定义了多个ImplicitNamingStrategy实现。应用程序也可以插入自定义实现。有多种方法可以指定要使用的ImplicitNamingStrategy。

首先,应用程序可以使用​​hibernate.implicit_naming_strategy​​配置设置指定实现,该配置设置接受:

  • 用于开箱即用的实现的预定义“短名称”
    default
    for org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl- 别名jpa
    jpa
    for org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl- 符合JPA 2.0标准的命名策略
    legacy-hbm
    for org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl- 符合原始的Hibernate NamingStrategy
    legacy-jpa
    for org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl- 符合为JPA 1.0开发的遗留NamingStrategy,遗憾的是,在很多方面都不清楚隐式命名规则
    component-path
    for org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl- 主要遵循ImplicitNamingStrategyJpaCompliantImpl规则,除了它使用完整的复合路径,而不仅仅是结束属性部分
  • 引用实现org.hibernate.boot.model.naming.ImplicitNamingStrategy的类

其次,应用程序和集成可以利用org.hibernate.boot.MetadataBuilder#applyImplicitNamingStrategy 指定要使用的ImplicitNamingStrategy。


② 配置ImplicitNamingStrategy

方法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);

方法三

SpringBoot整合JPA中在application.yml中配置:
Hibernate5中实体映射命名策略_spring_03


【2】PhysicalNamingStrategy

许多组织围绕数据库对象(表,列,外键等)的命名定义规则。PhysicalNamingStrategy的想法是帮助实现这样的命名规则,而不必通过显式名称将它们硬编码到映射中。

虽然ImplicitNamingStrategy的目的是确定一个名为accountNumbermap 的属性映射到accountNumber未明确指定的逻辑列名称,但PhysicalNamingStrategy的目的是,例如,应该说缩写物理列名称acct_num。

默认实现是简单地使用逻辑名称作为物理名称。但是,应用程序和集成可以定义此PhysicalNamingStrategy规则的自定义实现。

PhysicalNamingStrategy 接口源码如下:

public interface PhysicalNamingStrategy {
public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment jdbcEnvironment);

public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment jdbcEnvironment);

public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment);

public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment jdbcEnvironment);

public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment);
}

其只有一个实现:

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相同。

方法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());

方法三

SpringBoot整合JPA中在application.yml中配置:
Hibernate5中实体映射命名策略_hibernate_04

如上图配置所示,默认情况下如果Entity中不使用@Column指定属性对应数据库的列名,那么将会使用属性作为数据库列名。

实体类User如下:

@Entity 
@Table(name = "tb_user")
public class User implements Serializable{

@Id //这是一个主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键
private Integer id;
private String lastName;
private String email;
//...
}

使用方法三数据库生成属性如下:

`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL,
`lastName` varchar(255) DEFAULT NULL,

如果我们想要将其转换成last_name,除了使用@Column注解外,还可以自定义PhysicalNamingStrategy实现。

如下所示:

public class ImprovedNamingStrategy implements PhysicalNamingStrategy {

@Override
public Identifier toPhysicalCatalogName(Identifier identifier, JdbcEnvironment jdbcEnv) {
return convert(identifier);
}

@Override
public Identifier toPhysicalColumnName(Identifier identifier, JdbcEnvironment jdbcEnv) {
return convert(identifier);
}

@Override
public Identifier toPhysicalSchemaName(Identifier identifier, JdbcEnvironment jdbcEnv) {
return convert(identifier);
}

@Override
public Identifier toPhysicalSequenceName(Identifier identifier, JdbcEnvironment jdbcEnv) {
return convert(identifier);
}

@Override
public Identifier toPhysicalTableName(Identifier identifier, JdbcEnvironment jdbcEnv) {
return convert(identifier);
}

private Identifier convert(Identifier identifier) {
if (identifier == null || identifier.getText()==null||identifier.getText().equals("")) {
return identifier;
}

String regex = "([a-z])([A-Z])";
String replacement = "$1_$2";
String newName = identifier.getText().replaceAll(regex, replacement).toLowerCase();
return Identifier.toIdentifier(newName);
}
}

【3】SpringBoot中推荐的Spring 命名策略

SpringBoot包下主要有两个:SpringImplicitNamingStrategy和 SpringPhysicalNamingStrategy。

① SpringImplicitNamingStrategy

其继承默认的ImplicitNamingStrategyJpaCompliantImpl ,源码如下:

public class SpringImplicitNamingStrategy extends ImplicitNamingStrategyJpaCompliantImpl {

@Override
public Identifier determineJoinTableName(ImplicitJoinTableNameSource source) {
String name = source.getOwningPhysicalTableName() + "_"
+ source.getAssociationOwningAttributePath().getProperty();
return toIdentifier(name, source.getBuildingContext());
}

}

类继承示意图如下:
Hibernate5中实体映射命名策略_hibernate5命名策略_05


② SpringPhysicalNamingStrategy

源码如下:

public class SpringPhysicalNamingStrategy implements PhysicalNamingStrategy {

@Override
public Identifier toPhysicalCatalogName(Identifier name,
JdbcEnvironment jdbcEnvironment) {
return apply(name, jdbcEnvironment);
}

@Override
public Identifier toPhysicalSchemaName(Identifier name,
JdbcEnvironment jdbcEnvironment) {
return apply(name, jdbcEnvironment);
}

@Override
public Identifier toPhysicalTableName(Identifier name,
JdbcEnvironment jdbcEnvironment) {
return apply(name, jdbcEnvironment);
}

@Override
public Identifier toPhysicalSequenceName(Identifier name,
JdbcEnvironment jdbcEnvironment) {
return apply(name, jdbcEnvironment);
}

@Override
public Identifier toPhysicalColumnName(Identifier name,
JdbcEnvironment jdbcEnvironment) {
return apply(name, jdbcEnvironment);
}

private Identifier apply(Identifier name, JdbcEnvironment jdbcEnvironment) {
if (name == null) {
return null;
}
StringBuilder builder = new StringBuilder(name.getText().replace('.', '_'));
for (int i = 1; i < builder.length() - 1; i++) {
if (isUnderscoreRequired(builder.charAt(i - 1), builder.charAt(i),
builder.charAt(i + 1))) {
builder.insert(i++, '_');
}
}
return getIdentifier(builder.toString(), name.isQuoted(), jdbcEnvironment);
}

/**
* Get an identifier for the specified details. By default this method will return an
* identifier with the name adapted based on the result of
* {@link #isCaseInsensitive(JdbcEnvironment)}
* @param name the name of the identifier
* @param quoted if the identifier is quoted
* @param jdbcEnvironment the JDBC environment
* @return an identifier instance
*/
protected Identifier getIdentifier(String name, boolean quoted,
JdbcEnvironment jdbcEnvironment) {
if (isCaseInsensitive(jdbcEnvironment)) {
name = name.toLowerCase(Locale.ROOT);
}
return new Identifier(name, quoted);
}

/**
* Specify whether the database is case sensitive.
* @param jdbcEnvironment the JDBC environment which can be used to determine case
* @return true if the database is case insensitive sensitivity
*/
protected boolean isCaseInsensitive(JdbcEnvironment jdbcEnvironment) {
return true;
}

private boolean isUnderscoreRequired(char before, char current, char after) {
return Character.isLowerCase(before) && Character.isUpperCase(current)
&& Character.isLowerCase(after);
}

}

进行了大小写处理和"_"符号处理。

故而,我们也可以在SpringBoot环境下直接使用这两个命名策略。

application.yml配置文件如下:

spring: 
jpa:
database: mysql
hibernate:
ddl-auto: update
naming:
implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
show-sql: true

此时再次创建数据表,lastName属性将会自动转换成数据表中last_name列:

`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL,
`last_name` varchar(255) DEFAULT NULL,

参考hibernate官方文档:​​Hibernate官方用户指南​