对象模型到关系数据库的映射的一部分是将对象模型中的名称映射到相应的数据库名称。
首先说明一点,Hibernate5中不再支持hibernate.ejb.naming_strategy
属性配置,可以使用如下两个属性配置替换:
hibernate.implicit_naming_strategy hibernate.physical_naming_strategy
Hibernate5以前默认命名策略接口类示意图:
Hibernate5将此视为2阶段过程:
- 第一阶段是从域模型映射中确定正确的逻辑名称。逻辑名可以由用户显式指定(使用@Column或 @Table例如),也可以由Hibernate通过ImplicitNamingStrategy契约隐式确定 。
- 其次是将此逻辑名解析为PhysicalNamingStrategy合约定义的物理名称。
【1】 ImplicitNamingStrategy
当实体没有显式命名它映射到的数据库表时,我们需要隐式确定该表名。或者,当某个特定属性没有显式命名它映射到的数据库列时,我们需要隐式确定该列名。
org.hibernate.boot.model.naming.ImplicitNamingStrategy
当映射未提供显式名称时,有一些规则用于确定逻辑名称的示例。
① ImplicitNamingStrategy的实现
Hibernate定义了多个ImplicitNamingStrategy实现。应用程序也可以插入自定义实现。有多种方法可以指定要使用的ImplicitNamingStrategy。
首先,应用程序可以使用hibernate.implicit_naming_strategy
配置设置指定实现,该配置设置接受:
- 用于开箱即用的实现的预定义“短名称”
- default:org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl- 别名jpa
- jpa:org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl- 符合JPA 2.0标准的命名策略
- legacy-hbm:org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl- 符合原始的Hibernate NamingStrategy
- legacy-jpa:org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl- 符合为JPA 1.0开发的遗留NamingStrategy,遗憾的是,在很多方面都不清楚隐式命名规则
component-path: org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl- 主要遵循ImplicitNamingStrategyJpaCompliantImpl规则,除了它使用完整的复合路径,而不仅仅是结束属性部分
- 引用实现org.hibernate.boot.model.naming.ImplicitNamingStrategy的类
其次,应用程序和集成可以利用org.hibernate.boot.MetadataBuilder#applyImplicitNamingStrategy
指定要使用的ImplicitNamingStrategy。
② 配置ImplicitNamingStrategy
方法一
在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中配置:
【2】PhysicalNamingStrategy
① 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
相同。
② 配置PhysicalNamingStrategy
方法一
在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中配置:
如上图配置所示,默认情况下如果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()); } }
类继承示意图如下:
② 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官方用户指南