命名策略
命名策略接口
前面的章节,我们已经介绍了简单实体映射和关联映射 (一对一、多对一、一对多、多对多)。从这些内容中,我们了解到
-
可以使用
@Table(name = "...")
明确地为实体指定表名 -
可以使用
@GeneratedValue(..., sequenceName = "...")
明确地指定生成id所需的序列名称 (前提是使用序列增长策略) -
可以使用
@Column(name = "...")
明确地为普通列指定列名 -
可以使用
@JoinColumn(name = "...")
明确地为外键列指定列名 -
可以使用
@JoinTable(name = "...")
明确地为基于中间表的关联属性指定中间表表名以及其所有列名。
然而,为了提高开发效率,我们不可能过多地使用这些注解。大部分情况下,默认的名字推导行为应该可以工作,少数情况下,才在 代码中使用这些注解。
对于某个类或属性,如果用户不用这类注解,如何自动决定数据库中的标识名,被称为命名策略,是一个可定制的Java接口
package org.babyfish.jimmer.sql.meta;
import org.babyfish.jimmer.meta.ImmutableProp;
import org.babyfish.jimmer.meta.ImmutableType;
public interface DatabaseNamingStrategy {
String tableName(ImmutableType type);
String sequenceName(ImmutableType type);
String columnName(ImmutableProp prop);
String foreignKeyColumnName(ImmutableProp prop);
String middleTableName(ImmutableProp prop);
String middleTableBackRefColumnName(ImmutableProp prop);
String middleTableTargetRefColumnName(ImmutableProp prop);
}
其中,ImmutableType
和ImmutableProp
是Jimmer元数据所用类型,可以很直观地用JVM反射API的Class
和Field
来做类比理解。
各方法的作用如下
-
tableName: 已知一个实体类型,其表名是什么?
-
sequenceName: 已知一个id增长策略为序列的实体类型,其序列名是什么?
-
columName: 已知一个非关联属性,其列名是什么?
-
foreignKeyColumnName: 已知一个基于外键的关联属性,其列名是什么?
-
middleTableName: 已知一个基于中间表的关联属性,其中间表表名是什么?
-
middleTableBackRefColumnName: 已知一个基于中间表的关联属性,中间表中指向当前实体的外键的列名是什么?
-
middleTableTargetRefColumnName: 已知一个基于中间表的关联属性,中间表中指向关联实体的外键的列名是什么?
默认命名策略
大部分情况下,开发人员都无需直接实现此接口,Jimmer内置的org.babyfish.jimmer.sql.runtime.DefaultDatabaseNamingStrategy
类已经实现了此接口。
DefaultDatabaseNamingStrategy类有两个静态字段
-
UPPER_CASE:
生成的数据库标识名全部大写
信息如果用户不做任何配置,这就是Jimmer默认的命名策略。
-
LOWER_CASE:
生成的数据库标识名全部小写。
一些数据库,比如MySQL,可以配置是否大小写敏感。所以,你很有可能接手一个MySQL数据库,被设置为大小敏感模式且大部分表名和列名都是小写的,这时,你需要用此策略覆盖默认策略。
即使UPPER_CASE
和LOWER_CASE
都无法满足你的要求,你需要实现自己的策略,也可以考虑继承这个默认策略,而非从头实现。
在介绍默认策略的行为之前,我们先引入一个字符变换规则:snake。
所谓snake,即把大小写交替的文本转化为下划线拼接的文本,比如类名BookStore
的snake变形为BOOK_STORE
,属性名firstName
的snake变形为FIRST_NAME
。
考虑到大小写问题,我们定义两个函数, u_snake
和l_snake
,其行为如下
u_snake("BookStore")
-> "BOOK_STORE"l_snake("BookStore")
-> "book_store"u_snake("firstName")
-> "FIRST_NAME"l_snake("firstName")
-> "first_name"
有了u_snake
和l_snake
的规定后,我们很容易阐述DefaultDatabaseNamingStrategy
的行为
下文中的ClassName,指Java类的SimpleName,并非QualifiedName。
UPPER_CASE
-
tableName
规则:
u_snake(ClassName)
例子:BookStore -> BOOK_STORE
-
sequenceName
规则:
u_snake(ClassName)
_ID_SEQ例子:BookStore -> BOOK_STORE_ID_SEQ
-
columName
规则:
u_snake(ClassName)
例子:firstName -> FIRST_NAME
-
foreignKeyColumnName
规则:
u_snake(ClassName)
_ID例子:parentNode -> PARENT_NODE_ID
-
middleTableName
规则:
u_snake(SourceClassName)
_u_snake(TargetClassName)
_MAPPING例子:Book::authors -> BOOK_AUTHOR_MAPPING
-
middleTableBackRefColumnName
规则:
u_snake(SourceClassName)
_ID例子:Book::authors -> BOOK_ID
-
middleTableTargetRefColumnName
规则:
u_snake(TargetClassName)
_ID例子:Book::authors -> AUTHOR_ID
LOWER_CASE
-
tableName
规则:
l_snake(ClassName)
例子:BookStore -> book_store
-
sequenceName
规则:
l_snake(ClassName)
_id_seq例子:BookStore -> book_store_id_seq
-
columName
规则:
l_snake(ClassName)
例子:firstName -> first_name
-
foreignKeyColumnName
规则:
l_snake(ClassName)
_id例子:parentNode -> parent_node_id
-
middleTableName
规则:
l_snake(SourceClassName)
_l_snake(TargetClassName)
_mapping例子:Book::authors -> book_author_mapping
-
middleTableBackRefColumnName
规则:
l_snake(SourceClassName)
_id例子:Book::authors -> book_id
-
middleTableTargetRefColumnName
规则:
l_snake(TargetClassName)
_id例子:Book::authors -> author_id
覆盖策略
现在,我们来演示如何用DefaultDatabaseNamingStrategy.LOWER_CASE
覆盖默认的DefaultDatabaseNamingStrategy.UPPER_CASE
。
使用SpringBoot时
- Java
- Kotlin
@Bean
public DatabaseNamingStrategy databaseNamingStrategy() {
return DefaultDatabaseNamingStrategy.LOWER_CASE;
}
@Bean
fun databaseNamingStrategy(): DatabaseNamingStrategy =
DefaultDatabaseNamingStrategy.LOWER_CASE
不使用SpringBoot时
- Java
- Kotlin
JSqlClient sqlClient = JSqlClient
.newBuilder()
.setDatabaseNamingStrategy(
DefaultDatabaseNamingStrategy.LOWER_CASE
)
...省略其他配置...
.build();
val sqlClient = newKSqlClient {
setDatabaseNamingStrategy(
DefaultDatabaseNamingStrategy.LOWER_CASE
)
...省略其他配置...
}