Spring Boot JPA--命名策略

废话不多说,先来看下例子,首先定义一个Entity

@Entity
@Getter
@Setter
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String firstName;
    private String lastName;
    private String address;
    private LocalDate birthday;
}

然后定义一个repository

@Repository
public interface UserDao extends JpaRepository<User, Integer>{
}

最后增加相关DataSource配置,并启动项目

spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai
spring.datasource.password=root
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.jdbc.time_zone=Asia/Shanghai
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto=update

由于我们设置spring.jpa.show-sql为true,我们可以在控制台看到如下的输出

Hibernate: create table user (id integer not null auto_increment, address varchar(255), birthday date, first_name varchar(255), last_name varchar(255), primary key (id)) engine=MyISAM

可以看到程序自动帮我们创建了table,但是有没有发现字段的名称发生了变化

firstName --->  first_name
lastName ---> last_name

今天我们就来探究下SpringBoot中Hibernate字段名称的命名策略,以下示例都是采用2.1.4版本的SpringBoot

spring.jpa.hibernate

在SpringBoot中,Hibernate的相关配置都保存在HibernateProperties,它配置了ConfigurationProperties注解,会自动装载前缀为spring.jpa.hibernate的配置。

其中Naming是一个内部类,Hibernate字段映射策略就是在这里配置的。

可以看到Hibernate有两种策略,分别是implicitStrategyphysicalStrategy,并且默认使用SpringImplicitNamingStrategySpringPhysicalNamingStrategy

Hibernate将对象模型映射到关系数据库分为两个步骤

  1. 从对象模型中确定逻辑名称。逻辑名可以由用户显式指定(使用@Column或@Table),也可以隐式指定。
  2. 将逻辑名称映射到物理名称,也就是数据库中使用的名称。

implicitStrategy用于第一步隐式指定逻辑名称,而physicalStrategy则用于第二步中逻辑名称到物理名称的映射。

例如Entity中有个字段名为phoneNumber,我们期望它的逻辑名称为phone_numer,而在数据库中实际的物理名称为p_num。

两种策略的作用范围如下

phoneNumber ----implicitStrategy---> phone_number
phone_number ----physicalStrategy---> p_num

SpringBoot中可以通过下面的配置来设置策略

spring.jpa.hibernate.naming.implicit-strategy

spring.jpa.hibernate.naming.physical-strategy

PhysicalNamingStrategy

Hibernate定义了PhysicalNamingStrategy接口,并提供了一个默认实现,SpringBoot也提供了一种实现SpringPhysicalNamingStrategy

PhysicalNamingStrategyStandardImpl

PhysicalNamingStrategyStandardImpl的实现很简单,就是将逻辑名称原封不动的映射为物理名称

SpringPhysicalNamingStrategy


主要逻辑都在apply方法中,我们看下apply的实现

其中isUnderscoreRequired的实现如下

getIdentifier的实现如下

看到这是不是就明白了,apply方法其实就是将驼峰法改成用下划线来连接,同时将名称转化为小写

ImplicitNamingStrategy

Hibernate定义了ImplicitNamingStrategy接口,并且提供了4种实现。而SpringBoot也提供了一种实现,也就是上文提到的SpringImplicitNamingStrategy

为了更好的了解各种策略之间的区别,我们可以先声明几种Entity,在通过设置不同的策略来查看最终的结果。

//集合类
@Embeddable
@Data
public class EmbeddableElement {

    @Column(name = "quoted_field")
    private String quotedField;

    private String regularField;
}
@Entity
@Data
public class OwnedEntity {
    @Id
    private Long id;

    @ElementCollection
    @CollectionTable
    Set<EmbeddableElement> ownedElements;
}
@Entity
@Table(name = "dependentTable")
@Data
public class DependentEntity {
    @Id
    private long id;

    @ManyToOne
    MainEntity mainEntity;

    @ElementCollection
    @CollectionTable(name = "dependentElements")
    Set<EmbeddableElement> dependentElements;
}
@Entity
@Table(name = "mainTable")
@Data
public class MainEntity {
    @Id
    private long id;

    @ElementCollection
    private Set<EmbeddableElement> mainElements;

    @OneToMany(targetEntity = DependentEntity.class)
    Set<DependentEntity> dependentEntities;

    @OneToOne(targetEntity = OwnedEntity.class)
    OwnedEntity ownedEntity;
}

ImplicitNamingStrategyJpaCompliantImpl

我们首先来看ImplicitNamingStrategyJpaCompliantImpl,因为它是上述几种实现的基类。我们在配置文件中设置该策略,并设置PhysicalNamingStrategyStandardImpl,以便更好的查看逻辑名称的映射

spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
pring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

启动项目,我们可以得到如下生成表的语句

create table dependentElements (DependentEntity_id bigint not null, quoted_field varchar(255), regularField varchar(255)) 
create table dependentTable (id bigint not null, mainEntity_id bigint, primary key (id)) 
create table MainEntity_mainElements (MainEntity_id bigint not null, quoted_field varchar(255), regularField varchar(255)) 
create table mainTable (id bigint not null, ownedEntity_id bigint, primary key (id)) 
create table mainTable_dependentTable (MainEntity_id bigint not null, dependentEntities_id bigint not null, primary key (MainEntity_id, dependentEntities_id)) 
create table OwnedEntity (id bigint not null, primary key (id)) 
create table OwnedEntity_ownedElements (OwnedEntity_id bigint not null, quoted_field varchar(255), regularField varchar(255)) 

是不是有点混乱,没关系,我们一个个来看。首先来看table的逻辑名称,table可以分为几类

  1. primaryTable(主表)
  2. joinTable(关联表)
  3. collectionTable(集合表)

我们先来看主表逻辑名称的生成策略

create table mainTable (id bigint not null, ownedEntity_id bigint, primary key (id))

create table dependentTable (id bigint not null, mainEntity_id bigint, primary key (id))

create table OwnedEntity (id bigint not null, primary key (id)) 

由于MainEntity,DependentEntity都使用了@Table注解,所以逻辑名称是显式定义的,由注解中的name属性决定,而OwendEntity没有使用@Table注解,所以它的逻辑名称由ImplicitNamingStrategy决定。

我们可以看到ImplicitNamingStrategyJpaCompliantImpl直接将JpaEntityName,也即是Entity的类名(OwnedEntity)作为逻辑名称

再来看下关联表逻辑名称的生成策略

create table mainTable_dependentTable (MainEntity_id bigint not null, dependentEntities_id bigint not null, primary key (MainEntity_id, dependentEntities_id))

可以看到对于关联表的逻辑名称,是由Entity的物理名称 + ‘_’ + 引用Entity的物理名称组成

最后来看下集合表逻辑名称的生成策略

create table dependentElements (DependentEntity_id bigint not null, quoted_field varchar(255), regularField varchar(255)) 

create table MainEntity_mainElements (MainEntity_id bigint not null, quoted_field varchar(255), regularField varchar(255))

create table OwnedEntity_ownedElements (OwnedEntity_id bigint not null, quoted_field varchar(255), regularField varchar(255))

在DependentEntity中使用@CollectionTable(name = “dependentElements”)显式的指定了集合表的逻辑名称。而在MainEntity和OwnedEntity中没有显式指定,集合表的生成策略为Entity的类名称 + ‘_’ + 属性名称

ImplicitNamingStrategyJpaCompliantImpl符合JPA2.0命名规范

ImplicitNamingStrategyLegacyHbmImpl

它是Hibernate原始的命名策略,它与ImplicitNamingStrategyJpaCompliantImpl的差异在于关联表逻辑名称的生成策略

create table mainTable_dependentEntities (MainEntity_id bigint not null, dependentEntities bigint not null, primary key (MainEntity_id, dependentEntities))

可以看到它的策略是Entity的物理名称 + ‘_’ + 引用Entity的属性名称

ImplicitNamingStrategyLegacyJpaImpl

ImplicitNamingStrategyLegacyJpaImpl符合JPA1.0的命名规范,它与JPA2.0的规范差异主要在集合表和关联表上

对于集合表,它的策略是Entity的物理名称 + ‘_’ + 属性名称

create table mainTable_mainElements (mainTable_id bigint not null, quoted_field varchar(255), regularField varchar(255))

对于关联表,它的策略是如果引用Entity没有物理名称,则使用属性名称

ImplicitNamingStrategyComponentPathImpl

它与JAP2.0的命名规范基本相同,除了它属性(列名)的逻辑名称包含复合名称

create table MainEntity_mainElements (MainEntity_id bigint not null, quoted_field varchar(255), mainElements_regularField varchar(255))

我们可以看到属性regularField加上了前缀mainElements

SpringImplicitNamingStrategy

它是Spring规定的命名策略,它与JAP2.0命名规范的差异在于关联表,它的策略是Entity的物理名称 + ‘_’ + 属性名称

create table mainTable_dependentEntities (MainEntity_id bigint not null, dependentEntities_id bigint not null, primary key (MainEntity_id, dependentEntities_id))

用一张表格来说明下各个策略的差异

strategy primaryTable joinTable collectTable
ImplicitNamingStrategyJpaCompliantImpl Entity类名 Entity物理名称 + 引用Entity的物理名称 Entity类名 + 属性名称
ImplicitNamingStrategyLegacyHbmImpl - Entity物理名称 + 属性名称 -
ImplicitNamingStrategyLegacyJpaImpl - - Entity物理名称 + 属性名称
SpringImplicitNamingStrategy - Entity的物理名称 + 属性名称 -

总结

  1. Hibernate将对象映射到数据库分为两个步骤,一是entity映射到逻辑名称,而是逻辑名称映射到物理名称
  2. ImplicitNamingStrategy负责逻辑名称的映射,PhysicalNamingStrategy负责逻辑名称到物理名称的映射
  3. 可以通过实现ImplicitNamingStrategy和PhysicalNamingStrategy接口来定义命名策略
  4. SpringBoot中默认使用SpringImplicitNamingStrategy和SpringPhysicalNamingStrategy

参考文献


Reprint please specify: wbl Spring Boot JPA--命名策略

Previous
Spring Boot JPA--JpaRepository实现原理 Spring Boot JPA--JpaRepository实现原理
在SpringBoot中,我们只要定义好Entity,配置好DataSource,最后实现JpaRepository接口就可以对数据进行CRUD操作。 @Repository public interface UserDao extends
2020-04-13
Next
Spring Boot JPA--Query Spring Boot JPA--Query
在SpringBoot中,当我们实现JpaRepository接口,就可以对实例进行基本的CRUD操作。同时SpringBoot也提供了以下几种方式来满足一些复杂的查询需求。 @Query注解 Named Queries Specific
2020-04-02