JPA 与 Hibernate 命名策略

hibernate 默认的命名策略是不会将驼峰式命名 (cameCase) 映射到蛇形命名 (snake_case) 上去的,
虽然可以直接在 @Column 上指定需要映射的 name,但是使用策略一致地应用命名更方便些。

从 Hibernate 5 开始,数据库对象命名策略由 PhysicalNamingStrategy 接口表示,我们可以自定义该接口以自动将数据库标识符从 cameCase 转换为 snake_case。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class CamelCaseToSnakeCaseNamingStrategy 
extends PhysicalNamingStrategyStandardImpl {

public static final CamelCaseToSnakeCaseNamingStrategy INSTANCE =
new CamelCaseToSnakeCaseNamingStrategy();

public static final String CAMEL_CASE_REGEX = "([a-z]+)([A-Z]+)";

public static final String SNAKE_CASE_PATTERN = "$1\\_$2";

@Override
public Identifier toPhysicalCatalogName(
Identifier name,
JdbcEnvironment context) {
return formatIdentifier(
super.toPhysicalCatalogName(name, context)
);
}

@Override
public Identifier toPhysicalSchemaName(
Identifier name,
JdbcEnvironment context) {
return formatIdentifier(
super.toPhysicalSchemaName(name, context)
);
}

@Override
public Identifier toPhysicalTableName(
Identifier name,
JdbcEnvironment context) {
return formatIdentifier(
super.toPhysicalTableName(name, context)
);
}
@Override
public Identifier toPhysicalSequenceName(
Identifier name,
JdbcEnvironment context) {
return formatIdentifier(
super.toPhysicalSequenceName(name, context)
);
}

@Override
public Identifier toPhysicalColumnName(
Identifier name,
JdbcEnvironment context) {
return formatIdentifier(
super.toPhysicalColumnName(name, context)
);
}

private Identifier formatIdentifier(
Identifier identifier) {
if (identifier != null) {
String name = identifier.getText();

String formattedName = name
.replaceAll(
CAMEL_CASE_REGEX,
SNAKE_CASE_PATTERN)
.toLowerCase();

return !formattedName.equals(name) ?
Identifier.toIdentifier(
formattedName,
identifier.isQuoted()
) :
identifier;
} else {
return null;
}
}
}

如果你引用了 hibernate-types,则不需要该自定义策略:

1
2
3
4
5
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>

要使用自定义的策略类,只需要将该类配置给 hibernate.physical_naming_strategy 属性:

1
2
3
<property name="hibernate.physical_naming_strategy"
value="com.vladmihalcea.hibernate.type.util.CamelCaseToSnakeCaseNamingStrategy"
/>

在 Spring Data JPA 中该策略的默认配置为:
org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 是否驼峰,根据 aBa 类型判断
private boolean isUnderscoreRequired(char before, char current, char after) {
return Character.isLowerCase(before) && Character.isUpperCase(current)
&& Character.isLowerCase(after);
}

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);
}

隐式命名策略 (ImplicitNamingStrategy) 在缺省时提供相应的命名处理:

  • 实体主表: @Table
  • 连表: @JoinTable
  • 集合表: @CollectionTable
  • 列命名: @Column

Spring Data JPA 模式隐式命名策略为:
org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy