非结构化映射
非结构化设计案例
通过关联映射相关内容,不难发现,所有关联都是基于@JoinColumn和@JoinTable (可能被省略)。
虽然Jimmer支持伪外键 (在开发人员意识中是外键,但是数据库中并没有对应的外键约束),但是之前我们讨论的所有关联映射都是基于关系型数据库的3NF设计。
然而,你可能会接手一个遗留数据库,包含非范式化的数据库设计,例如
create table book(
id bigint unsigned not null auto_increment primary key,
author_ids varchar(100),
...省略其他字段...
) engine=innodb;
这里,author_ids是一个字符串列,其数据为当前Book所关联的所有Author对象的id用“逗号”拼接在一起,比如1, 5, 100, 206。
很明显,这是一个非结构化设计,不满足关系型数据库的3NF。
对于支持数组类型的Postgres而言,采用数组是更好的选择,但是,为了让示范有更好的普适性,这个例子仍然采用字符串拼接,不利用数据库产品的特有功能。
非结构化关联
为了支持这种非结构化设计,Jimmer提供了@org.babyfish.jimmer.sql.JoinSql注解。
@JoinSql必须也只能和@ManyToMany配合使用
接下来,创建一个SQL函数 (这里以MySQL为例)
create function concatenation_string_contains(
self varchar(100),
id bigint
) returns boolean
begin
if self is null then
return false;
end if;
return self regexp concat('(^|\\,)\\s*', id, '\\s*(\\,|$)');
end;
该函数假设参数self的格式是多个数字用逗号连接的结果,判断其中是否包含参数id。例如
-
concatenation_string_contains('100, 200, 300', 200)返回true -
concatenation_string_contains('100, 200, 300', 201)返回false
现在,我们可以映射非结构化的多对多关联
- Java
- Kotlin
@Entity
public interface Book {
@ManyToMany
@JoinSql(
"concatenation_string_contains(" +
" %alias.author_ids, " +
" %target_alias.id" +
")"
)
List<Author> authors();
...省略其他代码...
}
@Entity
interface Book {
@ManyToMany
@JoinSql(
"concatenation_string_contains(" +
" %alias.author_ids, " +
" %target_alias.id" +
")"
)
val authors: List<Author>
...省略其他代码...
}
@JsonSql使用的SQL表达式表示连接条件,其中
-
concatenation_string_contains: 我们之前定义的SQL函数 -
%alias: Jimmer约定的特殊符号,表示当前表 (在本例中为book表) 的alias -
%target_alias: Jimmer约定的特殊符号,表示目标表 (在本例中为author表) 的alias
ORM自动生成SQL (这对动态查询非常重要,尤其是Jimmer这种首次提出动态表连接概念的ORM),用户无法干预最终SQL中各表的alias。
因此,在此使用%alias和%target_alias是唯一的办法。
值得一提的是,如果想定义双向多对多关联,由于Book.authors已经使用了@JoinSql,另外一端的Author.books就没必要再次使用@JoinSql了,和以前一样,使用mappedBy表示自己是从动端属性即可
- Java
- Kotlin
@Entity
public interface Author {
@ManyToMany(mappedBy = "authors")
List<Book> books();
...省略其他代码...
}
@Entity
interface Author {
@ManyToMany(mappedBy = "authors")
val books: List<Book>
...省略其他代码...
}