Naming 命名策略详解及其实践
用 JPA 离不开 @Entity 实体,我都知道实体里面有字段映射,而字段映射的方法有两种:
- 显式命名:在映射配置时,设置的数据库表名、列名等,就是进行显式命名,即通过 @Column 注解配置。
- 隐式命名:显式命名一般不是必要的,所以可以选择当不设置名称,这时就交由 Hibernate 进行隐式命名,另外隐式命名还包括那些不能进行显式命名的数据库标识符,即不加 @Column 注解时的默认映射规则。
Naming 命名策略详解
我们通过源码发现:Naming 的源码发现 Hibernate 4 的时候隐式命名策略是 org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy;而我们发现 Hibernate 5 将隐式命名策略拆分成了两步:implicitStrategy 和 physicalStrategy。从官方的文档中得知这样做的目的是提高灵活性,减少构建命名策略过程中用到的重复的信息。
- 第一个阶段是从对象模型中提取一个合适的逻辑名称,这个逻辑名称可以由用户指定,通过 @Column 和 @Table 等注解完成,也可以通过被 Hibernate 的 ImplicitNamingStrategy 指定。
- 第二个阶段是将上述的逻辑名称解析成物理名称,物理名称是由 Hibernate 中的 PhysicalNamingStrategy 决定,两个阶段是有先后顺序的。
(1)ImplicitNamingStrategy
当一个实体对象没有显式的指明它要映射的数据库表或者列的名称时,在 Hibernate 内部就要为我们隐式处理,比如一个实体没有在 @Table 中的指明表名,那么表名隐式的被认为是实体名,或者 @Entity 中的提供的名称。比如一个实体没有在 @Column 中的指明列名,那么列名隐式的被认为是该实体对应的字段名,而我们看到源码默认的隐式策略采用的类是:“org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy”。
Hibernate 中定义了多个 ImplicitNamingStrategy 的实现,可以开箱即用,而之前的逻辑名名称就是物理名称这种策略只是其中一种,其他还包括:
- ImplicitNamingStrategyJpaCompliantImpl:默认的命名策略,兼容 JPA 2.0 的规范;
- ImplicitNamingStrategyLegacyHbmImpl:兼容 Hibernate 老版本中的命名规范;
- ImplicitNamingStrategyLegacyJpaImpl:兼容 JPA 1.0 规范中的命名规范;
- ImplicitNamingStrategyComponentPathImpl:大部分与 ImplicitNamingStrategyJpaCompliantImpl,但是对于 @Embedded 等注解标志的组件处理是通过使用 attributePath 完成的,因此如果我们在使用 @Embedded 注解的时候,如果要指定命名规范,可以直接继承这个类来实现。
看一下 UML 类图:
SpringImplicitNamingStrategy 继承 ImplicitNamingStrategyJpaCompliantImpl,从源码可以看到,对外键、链表查询、索引如果未定义,都有下划线的处理策略,而 table 和 column 名字都默认与字段一样。
(2)PhysicalNamingStrategy
在这个阶段中,是根据业务需要制定自己的命名规范,通过使用 PhysicalNamingStrategy 可以实现这些规则,而不需要将表名和列名通过 @Table 和 @Column 等注解显式指定,无论对象模型中是否显式地指定列名或者已经被隐式决定,PhysicalNamingStrategy 都会被调用。但是对于 ImplicitNamingStrategy,仅仅只有当没有显式地提供名称时才会使用,也就是说当对象模型中已经指定了 @Table 或者 @Entity 等 name 时,设置的 ImplicitNamingStrategy 并不会起作用。
所以可以看应用场景,我们可以根据实际需求来定义自己的策略是继承 ImplicitNamingStrategy 还是继承 PhysicalNamingStrategy,比如加上前缀 t_ 等等,或者使用分隔符“-”等等。
需要注意的是:PhysicalNamingStrategy 永远是在 ImplicitNamingStrategy 之后执行的,并且永远会被执行到。我们看下 PhysicalNamingStrategyStandardImpl 的源码:
public class PhysicalNamingStrategyStandardImpl implements PhysicalNamingStrategy, Serializable {public static final PhysicalNamingStrategyStandardImpl INSTANCE = new PhysicalNamingStrategyStandardImpl();@Overridepublic Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment context) {return name;}@Overridepublic Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment context) {return name;}@Overridepublic Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {return name;}@Overridepublic Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment context) {return name;}@Overridepublic Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {return name;}
}
默认情况下,使用就是这种策略,将 ImplicitNamingStrategy 传过来的逻辑名直接作为数据库中的物理名称,什么都没干直接返回。看下类图:
实际工作中的一些扩展
实际工作中,我们有这样的应用场景:当我们使用 MySQL 数据库的时候,一般架构师会给我们定义规范和要求,字段一般都是小写加 "_" 下划线组成,而我们的 Java 类当中都是驼峰式的字段命名方式。如果我们一个新的微服务,每个表都非常标准和规范,那么就可以让实体当中省去 @column 指定名称的过程。
定义自己的 PhysicalNamingStrategy 继承 PhysicalNamingStrategyStandardImpl 类即可内容如下:
package com.jack.model.naming;
public class MyPhysicalNamingStrategy extends PhysicalNamingStrategyStandardImpl {
//重载PhysicalColumnName方法,修改字段的物理名称。@Overridepublic Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {String text = warp(name.getText());if(Objects.equals(text.charAt(0) , '_')){text = text.replaceFirst("_","");}return super.toPhysicalColumnName(new Identifier(text,name.isQuoted()), context);}
//将驼峰式命名转化成下划线分割的形式public static String warp(String text){text = text.replaceAll("([A-Z])","_$1").toLowerCase();if(Objects.equals(text.charAt(0) , '_')){text = text.replaceFirst("_","");}return text;}
}
修改 application.properties 添加如下内容即可:
spring.jpa.hibernate.naming.physical-strategy=com.jack.model.naming.MyPhysicalNamingStrategy
总结:
不过在实际工作中还是建议大家用显式 @Table @Column 等注解的时候就指明比较好,而可以在我们扩展的 MyPhysicalNamingStrategy 做规则验证工作,避免有些程序员没有按照我们的数据库规范命名。
H2 Database
H2 Database 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,支持 SQL,同时 H2 提供了一个十分方便的 Web 控制台用于操作和管理数据库内容。是一种内存数据库,Spring Data JPA 官方也推荐使用,不过作者建议读者在实际生产环境,我们都是用来跑测试用例的。这样可以对各种关键的 DB 没有影响,因为它是每次运行创建一份新的数据库的。
使用起来比较方便,不需要安装 Server 和 Client 的任何软件和工具,只需要我们用的时候引用一个 Jar 就可以自带 SQL 的 Server 和 Client 的操作了,非常适合演示项目使用。
我们来看一个使用案例。
(1)Gradle 引入 H2 的 jar 包
testCompile group: 'com.h2database', name: 'h2', version: '1.3.148'
(2)配置 application.properties 如下:
//# JDBC Url to use H2 DB File for persisting
spring.datasource.url:jdbc:h2:./database/samspledb;AUTO_SERVER=TRUE
//# Use H2 DB Driver
spring.datasource.driverClassName:org.h2.Driver
(3)其他什么都用改,直接启动项目就可以操作我们默认的 JPA @Entity了。
spring.jpa.hibernate.ddl-auto=update
我们再加上 ddl-auto 的配置成 update,就可以完美实现内存数据库方式的 test case 了。