Join Module: Iteration #6 Nested Join

系列文章目录

  1. Join-in-Memory一种基于注解配置的连表思路和实现
  2. Join Module: Iteration #1 AfterJoin
  3. Join Module: Iteration #2 Grouped Join
  4. Join Module: Iteration #3 JoinAtReturn
  5. Join Module: Iteration #4 Join Batch Control
  6. Join Module: Iteration #5 Dynamic Join Fields

文章目录


前言

场景: 聚合根关联一个子域列表, 子域还有关联的其他数据;

  • 例如: 合同+多个订单+一个订单多个商品, 形成了多层关联关系的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提供了大量能使我们快速便捷地处理数据的函数和方法。

相关推荐
Y‍waiX‍‍‮‪‎⁠‌‫‎‌‫‬2 小时前
CentOS7安装多版本jdk并切换jdk版本
java·jdk·centos
疯狂敲代码的老刘2 小时前
MyBatis Generator GUI 下载安装教程 可视化MyBatis代码生成
java·mybatis·mybatis-ui
追随者永远是胜利者2 小时前
(LeetCode-Hot100)23. 合并 K 个升序链表
java·算法·leetcode·链表·go
Moshow郑锴2 小时前
Java SpringBoot 疑难 Bug 排查思路解析:从“语法正确”到“行为相符”
java·spring boot·bug
APIshop2 小时前
淘宝商品评论接口实战解析:从抓包到数据抓取全链路技术指南
java·python
百锦再3 小时前
线程安全的单例模式全方位解读:从原理到最佳实践
java·javascript·安全·spring·单例模式·kafka·tomcat
百锦再3 小时前
Java synchronized关键字详解:从入门到原理(两课时)
java·开发语言·struts·spring·kafka·tomcat·maven
油丶酸萝卜别吃3 小时前
什么是 Java 内存模型(JMM)?
java·开发语言
量子炒饭大师3 小时前
【C++入门】Cyber神经的义体插件 —— 【类与对象】内部类
java·开发语言·c++·内部类·嵌套类