系列文章目录
- Join-in-Memory一种基于注解配置的连表思路和实现
- Join Module: Iteration #1 AfterJoin
- Join Module: Iteration #2 Grouped Join
- Join Module: Iteration #3 JoinAtReturn
- Join Module: Iteration #4 Join Batch Control
- Join Module: Iteration #5 Dynamic Join Fields
文章目录
- 系列文章目录
- 前言
- 一、设计
-
- 思路
- [NestedJoin 注解](#NestedJoin 注解)
- 构建嵌套Join执行器
- 动态提取对应层级的源数据
- 二、使用步骤
- 总结
前言
场景: 聚合根关联一个子域列表, 子域还有关联的其他数据;
- 例如: 合同+多个订单+一个订单多个商品, 形成了多层关联关系的Join链路
此场景下, 此前可以利用@AfterJoin功能, 实现商品的Join, 但效率不好, 具体原因不在这里赘述了.
@AfterJoin的设计目的是为了做CPU计算任务, 而不是I/O任务的. 在其中处理I/O任务将显著的增加整个Join业务的执行时间, 所以需要另一种方式来实现嵌套Join.
一、设计
思路
不说太细致, 可以去看代码.
通过注解标记需要嵌套处理的属性, 根据属性进入对应类型, 递归执行Join扫描, 并记录嵌套路径, 最后构造Join属性执行器. 在执行时, 根据嵌套路径和层级, 从源数据中按路径提取数据后执行对应嵌套层的Join业务.
因为嵌套Join需要在递归完成后才能构建一个完整的拓扑关系, 所以分组Join的分组逻辑也需要在处理器创建后再分组, 此前是可以提前将属性分组后再构造分组执行器.
- 对于 Join 操作, 内层的执行依赖外层数据, 所以内层的执行顺序由外层的执行顺序+层级的方式重新计算
- 对于 After Join 操作, 由于框架内定义AfterJoin在Join完成之后处理. 此时所有的关联数据已经设置完成, 那么不论哪个层级的AfterJoin任务都可以在被执行时(同时)拿到对应的数据, 此时执行层级就按照注解定义的来
NestedJoin 注解
标注属性, 提示框架需要进行嵌套扫描
java
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NestedJoin {
}
构建嵌套Join执行器
java
private <DATA, JOIN_KEY, JOIN_DATA, JOIN_RESULT> void createNestedJoinFieldExecutor(JoinContext<DATA> context, List<AbstractJoinFieldV2Executor<DATA, JOIN_KEY, JOIN_DATA, JOIN_RESULT>> holder) {
final Class<?>[] clazz = {context.getCurrentClass()};
final List<Field> fields = context.getCurrentClassFields();
fields.forEach(field -> {
if (AnnotatedElementUtils.isAnnotated(field, NestedJoin.class)) {
if (JoinContext.invalidFieldType(clazz, field)) {
return;
}
// 记录递归路径
context.enter(field, clazz[0]);
createNestedJoinFieldExecutor(context, holder);
}
// 递归从这里出来. 所以 clazz 并不会被更新
if (AnnotatedElementUtils.isAnnotated(field, annotationClass)) {
holder.add(createJoinFieldExecutor(context, field, AnnotatedElementUtils.getMergedAnnotation(field, annotationClass)));
}
});
// 返回外层
context.exit();
}
动态提取对应层级的源数据
例如: 合同 - 订单 - 商品 三层, 当join发生在 商品层 时, 源数据实际为 订单, 而不是合同, 所以需要从用户提交的源数据中, 提取对应层级的"层级源数据".
定义提取逻辑
java
@SuppressWarnings("unchecked")
static <DATA> Collection<DATA> goDownThePath(SpELHelper spELHelper, LinkedList<String> hierarchicalPaths, Collection<DATA> dataCollection) {
if (CollUtil.isEmpty(hierarchicalPaths)) {
return dataCollection;
}
final String path = hierarchicalPaths.pollFirst();
log.trace("Go down the path: the path is {}", path);
final List<DATA> list = dataCollection.stream()
.flatMap(data -> {
final Object apply = spELHelper.newGetterInstance(path).apply(data);
if (Objects.isNull(apply)) {
return Stream.empty();
}
Collection<DATA> extracted;
if (Collection.class.isAssignableFrom(apply.getClass())) {
extracted = (Collection<DATA>) apply;
} else {
extracted = List.of((DATA) apply);
}
if (CollUtil.isEmpty(hierarchicalPaths)) return extracted.stream();
return goDownThePath(spELHelper, hierarchicalPaths, extracted).stream();
})
.toList();
log.trace("Go down the path: the extracted data is {}", list);
return list;
}
@Override
@SuppressWarnings("unchecked")
protected <DATA> BiFunction<DATA, List<String>, Collection<DATA>> createExtractSourceData(JoinContext<DATA> context, Field field, JoinInMemory annotation) {
return (data, hierarchicalPaths) -> {
final LinkedList<String> modifiablePaths = Lists.newLinkedList(hierarchicalPaths);
Collection<DATA> dataCollection;
if (Collection.class.isAssignableFrom(data.getClass())) {
dataCollection = (Collection<DATA>) data;
} else {
dataCollection = Lists.newArrayList(data);
}
if (CollUtil.isEmpty(hierarchicalPaths)) return dataCollection;
return goDownThePath(spELHelper, modifiablePaths, dataCollection);
};
}
运行时提取
java
@Override
public void execute(Collection<SOURCE_DATA> sourceDataList) {
if (CollUtil.isEmpty(sourceDataList)) return;
// 针对 nested join 提供支持
final List<SOURCE_DATA> list = sourceDataList.stream().flatMap(data -> extractSouceData(data).stream()).toList();
// 这里的数据实际时根据嵌套层级展开后的数据
doExecute(list);
}
二、使用步骤
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String username;
@NestedJoin
@JoinOrderOnUserId("id")
// private List<Order> orders;
private Order order;
public User(Long id, String username) {
this.id = id;
this.username = username;
}
@AfterJoin
public void userAfterJoin() {
System.out.println("User After Join!!!");
}
}
@Data
@NoArgsConstructor
public class Order {
private Long id;
private Long userId;
@NestedJoin
@JoinOrderItemOnOrderId(value = "id")
private List<OrderItem> orderItems;
public Order(Long id, Long userId) {
this.id = id;
this.userId = userId;
}
@AfterJoin
public void orderAfterJoin() {
System.out.println("Order After Join!!!");
}
}
@Data
@NoArgsConstructor
public class OrderItem {
private Long id;
private Long orderId;
@NestedJoin
@JoinPurchaseOnOrderItemId("id")
private List<Purchase> purchases;
public OrderItem(Long orderId, Long id) {
this.orderId = orderId;
this.id = id;
}
@AfterJoin
public void orderItemAfterJoin() {
System.out.println("OrderItem After Join!!!");
System.out.println("this.purchases = " + this.purchases);
}
}
@Data
public class Purchase {
private Long orderItemId;
private Long productId;
public Purchase(Long orderItemId, Long productId) {
this.orderItemId = orderItemId;
this.productId = productId;
}
@AfterJoin
public void afterJoin() {
System.out.println("Purchase After Join: " + this);
}
}
测试
java
@Test
public void givenSpELHelper_WhenJoinWithNestedJoin_thenSuccess() {
// given
User user = new User(1L, "User_1");
// when
joinService.joinInMemory(user);
user = new User(1L, "User_1");
joinService.joinInMemory(user);
// then
// final List<Order> orders = user.getOrders();
// Assertions.assertThat(orders).isNotEmpty().size().isEqualTo(1);
// Assertions.assertThat(orders).element(0).isNotNull().extracting(Order::getOrderItems).isNotNull();
final Order order1 = user.getOrder();
final List<OrderItem> orderItems = order1.getOrderItems();
final OrderItem orderItem1 = orderItems.getFirst();
Assertions.assertThat(orderItem1).isNotNull().extracting(OrderItem::getPurchases).isNotNull();
final List<Purchase> purchases = orderItem1.getPurchases();
Assertions.assertThat(purchases).isNotEmpty().size().isEqualTo(1);
}

2.读入数据
代码如下(示例):
c
data = pd.read_csv(
'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv')
print(data.head())
该处使用的url网络请求的数据。
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。