废话不多说,先来看下例子,首先定义一个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有两种策略,分别是implicitStrategy和physicalStrategy,并且默认使用SpringImplicitNamingStrategy和SpringPhysicalNamingStrategy
Hibernate将对象模型映射到关系数据库分为两个步骤
- 从对象模型中确定逻辑名称。逻辑名可以由用户显式指定(使用@Column或@Table),也可以隐式指定。
- 将逻辑名称映射到物理名称,也就是数据库中使用的名称。
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可以分为几类
- primaryTable(主表)
- joinTable(关联表)
- 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的物理名称 + 属性名称 | - |
总结
- Hibernate将对象映射到数据库分为两个步骤,一是entity映射到逻辑名称,而是逻辑名称映射到物理名称
- ImplicitNamingStrategy负责逻辑名称的映射,PhysicalNamingStrategy负责逻辑名称到物理名称的映射
- 可以通过实现ImplicitNamingStrategy和PhysicalNamingStrategy接口来定义命名策略
- SpringBoot中默认使用SpringImplicitNamingStrategy和SpringPhysicalNamingStrategy