Link Relationship
Trantor 通过在模型上定义关联关系字段来描述模型间关系,支持所有 OneToOne 、OneToMany 、ManyToOne 和 ManyToMany 四种关系。
Link
- 查找(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 有一个查找关系到购买者即可。
@Modelpublic 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_REPLACESELECT `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. 通过关系反查
即通过目标模型的查找类型字段,反向查询对多的数据。 下例就是根据订单中的购买者字段,来反向查询购买者有那些订单。
@Modelpublic 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_REPLACESELECT `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,分别是订单和商品,订单上会声明该订单有那些商品,所以是一个一对多的关系。
@Modelpublic 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 的最主要区别,就是需要声明一个中间模型来承载多对多关系。 然后使用侧可以通过中间表反查的方式,来查询到对多的数据。
// 中间关联模型@Modelpublic class UserRoleRelation extends BaseModel<Long> { @Field @LinkMeta private User user;
@Field @LinkMeta private Role role;}
@Modelpublic class User extends BaseModel<Long> { @Field @LookupMeta(linkField = UserRoleRelation.user_field) @JunctionMeta(model = UserRoleRelation.class, lookupField = UserRoleRelation.role_field) private List<Role> roles;}
@Modelpublic 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_REPLACESELECT `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' ) );