跳转到内容

Link Relationship

Trantor 通过在模型上定义关联关系字段来描述模型间关系,支持所有 OneToOne 、OneToMany 、ManyToOne 和 ManyToMany 四种关系。

  • 查找(Link):就是在当前模型新建一个字段,声明关联一个目标模型。
    • 查找可以理解为一对一或多对一
    • 支持关联查询

LinkMany

  • 对多查找(LinkMany):跟 Link 类似,只是声明的字段类型是模型对象集合,会以 Json 格式存放关联的记录 id 数组

    • 数据逻辑上来说,也可以组成多对多,但是反查会有问题
    • 因为存的是一个 Json 串,所以本质上会和一般的查询有很多差别,查询方式也会有一些限制,使用场景上要多考虑。
    • 不支持关联查询

Lookup

  • 反向查找(Lookup):反向查找不会创建新的字段,本质上声明一个 Link 的反向查询,只是一个查询关系,不可写入。

    • 本质上就是目标模型的目标字段 = 当前记录的 ID
    • 支持关联查询

Junction

  • Link 和 Lookup 分别解决了 ToOne(一对一、多对一和一对多)的关联以及查询声明,如果有多对多的场景,则需要通过 Junction Lookup 的方式来声明。
    • Junction 可以理解为多对多关系中的中间模型(枢纽模型 / 连接模型 / 交叉模型),通过在中间模型定义两个中间模型对目标模型的多对一关联(Link),从而完成两边模型的多对多关系
    • 支持关联查询

Trantor release/0.16 版本之前,使用的是 Relation 的方式定义模型关联,但是由于存在一些缺陷,在 0.16 时开始废弃,启用了新的关联定义,即 Link。

Link 可以理解为是弱化版 Relation,本质是期望后期支持分库,所以 Link 和 Relation 还是会有一些区别的。

考虑到后期维护等方面,Link 和 Relation 的机制会共存一段时间,后面 Relation 会在 1.0 版本弃用。

具体业务场景

1. 一对一,多对一

有模型 Order 和 Buyer,订单只能有一个购买者,但是购买者可以有多个订单,所以本质上是一个多对一的关系。 类似于这种场景,直接声明 Order 有一个查找关系到购买者即可。

@Model
public class Order extends BaseModel<Long> {
@Field
@LinkMeta
private Buyer buyer;
}

场景:查询 Order 以及 Order 对应的 buyer 信息

[T-SQL]
select id,buyer.* from trade_Order;

上面的 T-SQL 对应的物理 SQL 为

# 执行结果 trade__order.buyer 临时存储在 DATASTORE_TO_REPLACE
SELECT
`id`,
`buyer` AS `buyer`
FROM
`trade__order`;
SELECT
`name`,
`id`,
`isDeleted`,
`createdAt`,
`updatedAt`,
`deletedAt`,
`banks` AS `banks`,
`linkBank` AS `linkBank`
FROM
`trade__buyer`
WHERE
`id` IN (DATASTORE_TO_REPLACE);

场景:根据 buyer 条件查询 Order

[T-SQL]
select * from trade_Order where buyer.`name` = 'test';

实际物理 SQL

SELECT
`id`,
`isDeleted`,
`createdAt`,
`updatedAt`,
`deletedAt`,
`buyer` AS `buyer`,
`items` AS `items`
FROM
`trade__order`
WHERE
`buyer` IN (
SELECT
`id`
FROM
`trade__buyer`
WHERE
`name` = 'test'
);

DataStore 最终会对该模型生成的 DDL 大致如下(忽略模块):

CREATE TABLE `order` (
`id` bigint(20) NOT NULL,
`buyer` varchar(256) COLLATE utf8mb4_unicode_ci NULL,
`createdAt` datetime NOT NULL COMMENT '创建时间 ',
`updatedAt` datetime NOT NULL COMMENT '更新时间 ',
`UpdatedBy` bigint(20) DEFAULT NULL COMMENT '最后修改人 ',
`CreatedBy` bigint(20) DEFAULT NULL COMMENT '创建人 ',
`isDeleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除 ',
PRIMARY KEY (`id`),
KEY `buyer` (`buyer`),
KEY `UpdatedBy` (`UpdatedBy`),
KEY `CreatedBy` (`CreatedBy`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

其中 buyer 字段存储的是 Buyer 模型的 id 。

2. 一对多

如果是反过来,Buyer 想看其购买的所有订单,就会有多种配置方式。 特性各有不同,可根据场景自行选择。

1. 通过关系反查

即通过目标模型的查找类型字段,反向查询对多的数据。 下例就是根据订单中的购买者字段,来反向查询购买者有那些订单。

@Model
public class Buyer extends BaseModel<Long> {
// 相当于查询 Order 中 buyer 字段等于当前记录 ID 的数据
@Field
@LookupMeta
private List<Order> orders;
}

需要注意 @LookupMeta 注解上有一个 linkField 参数,在上面的例子中 Order 中只有一个 Link 类型指向本模型的 buyer 字段,所以 trantor 会自动推断是通过 buyer 字段进行反查的,如果有 Lookup 到的模型中有多个 Link 到本模型的字段,这里就需要显式声明 linkField。

反向查询不会生成任何真实的字段,而且不可修改,单纯是一个查询关系

场景:查询 Buyer 以及 Buyer 对应的 order 信息

[T-SQL]
select `id``name``orders`.* from trade_Buyer;

实际物理SQL

# 执行结果 trade__buyer.id 临时存储在 DATASTORE_TO_REPLACE
SELECT
`id`,
`name`
FROM
`trade__buyer`;
SELECT
`id`,
`isDeleted`,
`createdAt`,
`updatedAt`,
`deletedAt`,
`buyer` AS `buyer`
FROM
`trade__order`
WHERE
`buyer` IN (DATASTORE_TO_REPLACE);

场景:根据 order 条件查询 Buyer

[T-SQL]
select * from trade_Buyer where `orders`.`code` = 'Order20210101122022';

实际物理SQL

SELECT
`id`,
`name`,
`isDeleted`,
`createdAt`,
`updatedAt`,
`deletedAt`
FROM
`trade__buyer`
WHERE
`id` IN (
SELECT
`buyer`
FROM
`trade__order`
WHERE
`code` = 'Order20210101122022'
);

2. 声明 LinkMany

除了根据多对一关系反查之外,还可以通过 LinkMany 的方式声明一对多关系。

有模型 Order 和 Item,分别是订单和商品,订单上会声明该订单有那些商品,所以是一个一对多的关系。

@Model
public class Order extends BaseModel<Long> {
@Field
@LinkMeta
private List<Item> items;
}

LinkMany 会在当前模型创建一个 Json 字段,用于存放对多的记录 ID 数组,所以 LinkMany 是可以写的字段,而 Lookup 只能查询,不能写。

同时 LinkMany 也会有一些查询性能方面的问题,因为是一个 Json 数组来存储,所以很多索引并不好处理。

DataStore 最终会对该模型生成的 DDL 大致如下(忽略模块):

CREATE TABLE `order` (
`id` bigint(20) NOT NULL,
`items` JSON DEFAULT NULL,
`createdAt` datetime NOT NULL COMMENT '创建时间 ',
`updatedAt` datetime NOT NULL COMMENT '更新时间 ',
`UpdatedBy` bigint(20) DEFAULT NULL COMMENT '最后修改人 ',
`CreatedBy` bigint(20) DEFAULT NULL COMMENT '创建人 ',
`isDeleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除 ',
PRIMARY KEY (`id`),
KEY `UpdatedBy` (`UpdatedBy`),
KEY `CreatedBy` (`CreatedBy`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

其中 items 字段存储的是 Item 模型的 id 数组,格式为 json 。

3. 多对多

多对多在业务场景中也经常会使用,我们用用户(User)和角色(Role)来举例,他们是多对多关系,User 可以有多个 Role,Role 也可以被多个 User 关联,所以我们会有一个 User 和 Role 之间的中间模型,我们暂时命名为 UserRoleRelation,可以给这个中间模型增加两个 Link 字段,用来关联 User 和 Role,使用示例请看下文。

这里跟原来 Trantor 的多对多 Relation 的最主要区别,就是需要声明一个中间模型来承载多对多关系。 然后使用侧可以通过中间表反查的方式,来查询到对多的数据。

// 中间关联模型
@Model
public class UserRoleRelation extends BaseModel<Long> {
@Field
@LinkMeta
private User user;
@Field
@LinkMeta
private Role role;
}
@Model
public class User extends BaseModel<Long> {
@Field
@LookupMeta(linkField = UserRoleRelation.user_field)
@JunctionMeta(model = UserRoleRelation.class, lookupField = UserRoleRelation.role_field)
private List<Role> roles;
}
@Model
public class Role extends BaseModel<Long> {
@Field
@LookupMeta(linkField = UserRoleRelation.role_field)
@JunctionMeta(model = UserRoleRelation.class, lookupField = UserRoleRelation.user_field)
private List<User> users;
}

多对多的反查,需要额外声明 junctionModel 来指定中间关联模型,并通过 LinkField 来声明要反查的字段。

场景:查询 User 以及 User 对应的 roles 信息

select `id`, `createdAt`, roles.* from base_User;

上面的SQL对应的物理SQL为

# 执行结果中的 base__user.id 临时存储在 DATASTORE_TO_REPLACE
SELECT
`id`,`createdAt`
FROM
`base__user`;
SELECT
`main`.`id`,
`main`.`name`,
`main`.`code`,
`main`.`isDeleted`,
`main`.`createdAt`,
`main`.`updatedAt`,
`main`.`deletedAt`,
`ds_middle_table`.`user` AS `ds_middle_table_join_field_alias`
FROM
`base__role` AS `main`
LEFT JOIN `base__user_role_relation` AS `ds_middle_table` ON `ds_middle_table`.`role` = `main`.`id`
WHERE
`ds_middle_table`.`user` IN (DATASTORE_TO_REPLACE);

场景:根据 roles 条件查询 User

select * from base_User where roles.`code` = 'xxx';

实际物理SQL

SELECT
`id`,
`name`,
`isDeleted`,
`createdAt`,
`updatedAt`,
`deletedAt`
FROM
`base__user`
WHERE
`id` IN (
SELECT
`user`
FROM
`base__user_role_relation`
WHERE
`role` IN (
SELECT
`id`
FROM
`base__role`
WHERE
`code` = 'xxx'
)
);