跳到主要内容

非结构化映射

非结构化设计案例

通过关联映射相关内容,不难发现,所有关联都是基于@JoinColumn@JoinTable (可能被省略)

虽然Jimmer支持伪外键 (在开发人员意识中是外键,但是数据库中并没有对应的外键约束),但是之前我们讨论的所有关联映射都是基于关系型数据库的3NF设计。

然而,你可能会接手一个遗留数据库,包含非范式化的数据库设计,例如

create table book(
id bigint unsigned not null auto_increment primary key,
// highlight-next-line
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

现在,我们可以映射非结构化的多对多关联

Book.java
@Entity
public interface Book {

@ManyToMany
@JoinSql(
"concatenation_string_contains(" +
" %alias.author_ids, " +
" %target_alias.id" +
")"
)
List<Author> authors();

...省略其他代码...
}

@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表示自己是从动端属性即可

Author.java
@Entity
public interface Author {

@ManyToMany(mappedBy = "authors")
List<Book> books();

...省略其他代码...
}

注意事项

注意

除非必须要兼容已有数据库,否则不要进行非结构化设计并用@JoinSql进行映射,因为非结构化多对多关联存在很多限制: