避免 in-memory 计算 OneToMany ... 在获取 MAX 1 Child 时
Avoid in-memory calculation for OneToMany ... when fetching MAX 1 Child
我正在使用 Spring Data JPA 并拥有映射到各自表的实体。我需要查询结果,以便我可以根据它们的 strength
获取所有 parent 和每个 parent 一个 child。
@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>{}
一些规则和假设:
- 一个child属于一个parent
- 一个parent在DB
中可以有多个childobjects
- A parent 可以有 0 或 1 个 child 强度 = STRONG
- A parent 将有 1 个 child 强度 = NORMAL
- A parent 将有 0 个或更多 child objects 强度 = 弱
- 弱 child 永远不会 returned
- 下面的
getParentAndStrongChildren
方法应该 return 最多 1 child。
我可以在 Spring 中对 Parent 存储库方法进行 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注解来实现吗?
这是典型的“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
或者,使用 window 函数(也是标准 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
我正在使用 Spring Data JPA 并拥有映射到各自表的实体。我需要查询结果,以便我可以根据它们的 strength
获取所有 parent 和每个 parent 一个 child。
@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>{}
一些规则和假设:
- 一个child属于一个parent
- 一个parent在DB 中可以有多个childobjects
- A parent 可以有 0 或 1 个 child 强度 = STRONG
- A parent 将有 1 个 child 强度 = NORMAL
- A parent 将有 0 个或更多 child objects 强度 = 弱
- 弱 child 永远不会 returned
- 下面的
getParentAndStrongChildren
方法应该 return 最多 1 child。
我可以在 Spring 中对 Parent 存储库方法进行 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注解来实现吗?
这是典型的“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
或者,使用 window 函数(也是标准 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