【问题标题】:Avoid in-memory calculation for OneToMany ... when fetching MAX 1 Child在获取 MAX 1 Child 时避免 OneToMany 的内存计算...
【发布时间】:2019-05-09 16:17:23
【问题描述】:

我正在使用 Spring Data JPA 并拥有映射到各自表的实体。我需要查询结果,以便我可以根据他们的strength 获取所有父母和每个父母一个孩子。

@Entity
public class Parent {
  @Id
  Long id;

  @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
  private List<Child> children;
}

@Entity
public class Child {
  @Id
  Long id;

  @Enumerated(EnumType.STRING)
  private Strength strength;

  @ManyToOne(fetch = FetchType.EAGER)
  @JoinColumn(name = "parent_id", nullable = false, insertable = true, updatable = false)
  private Parent parent;
} 

public Enum Strength {
  STRONG,
  NORMAL,
  WEAK
}

我有一个基本的 crud 存储库,如下所示:

@Repository
public interface ParentRepository extends CrudRepository<Parent, Long>{}

一些规则和假设:

  1. 孩子属于单亲家庭
  2. 一个父对象在 DB 中可以有多个子对象
  3. 父母可以有 0 或 1 个力量 = STRONG 的孩子
  4. 父母将有 1 个力量 = 正常的孩子
  5. 父级将有 0 个或多个子对象的强度 = WEAK
  6. 软弱的孩子永远不会回来
  7. 下面的 getParentAndStrongChildren 方法最多应返回 1 个子项。

我可以在 Spring 中对 Parent Repository 方法进行 findAll 查询,然后将结果映射到内存中,如下所示

public List<Parent> getParentAndStrongChildren(){
    List<Parent> parents = parentRepository
        .findAll().stream()
        .map(p -> {
            if(p.getChildren() != null && p.getChildren.size() > 1){
               Child found = p.getChildren().stream()
                            .filter(c -> c.getStrength() == Strength.STRONG)
                            .findFirst()
                            .orElseGet(()-> p.getChildren().stream()
                                             .filter(c -> c.getStrength() == Strength.NORMAL)
                                             .findFirst()   
                                             .orElse(null));
                p.setChildren(found == null ? null : new Arrays.asList(found));
            }
        }
    return parents;
}

问:有没有什么办法不在内存中做过滤,而依靠JPQL和@Query注解来实现呢?

【问题讨论】:

  • 你有很多逻辑,也许只有原生 SQL 才能为你完成这项工作。你能在 SQL 查询上做那个逻辑吗?我们将最容易帮助您使用 SQL 查询。

标签: java spring java-8 spring-data-jpa jpql


【解决方案1】:

这是一个典型的“top N per category”SQL 查询。我个人怀疑这可以用 JPQL 完成,但也许其他人会提供答案。这是使用横向的标准 SQL 解决方案:

SELECT p.*
FROM parent p
LEFT JOIN LATERAL (
  SELECT c.*
  FROM child c
  WHERE p.id = c.parent_id
  AND c.strength != 'WEAK'
  ORDER BY CASE c.strength WHEN 'STRONG' THEN 1 WHEN 'NORMAL' THEN 2 END
  FETCH FIRST ROW ONLY
) c ON 1 = 1

或者,使用窗口函数(也是标准 SQL):

SELECT p.*
FROM parent p
LEFT JOIN (
  SELECT c.*, row_number() OVER (
    PARTITION BY c.parent_id 
    ORDER BY CASE c.strength WHEN 'STRONG' THEN 1 WHEN 'NORMAL' THEN 2 END
  ) AS rk
  FROM child c
  WHERE c.strength != 'WEAK'
) c ON p.id = c.parent_id AND c.rk = 1

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多