当使用 JPA Hibernate 将子项和父项存储在同一个 table 中时,在当前和所有后续父项中搜索提供的值

Search with the supplied value in the current and all successive parents when both child and parent are stored in the same table using JPA Hibernate

我有两个实体:

使用聚合器映射为 OneToOne 的影响详细信息

/**
 * @author MalkeithSingh on 27-08-2019
 */
@Entity
@Table(name = "INFLUENCE_DETAILS")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
@JsonNaming(PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy.class)
public class InfluenceDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;



    @Column(name = "influence_summary_id", length = 64)
    private Long influenceSummaryId;

    @OneToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "aggregator_id", nullable = false, referencedColumnName = "agg_id",
                updatable = false, insertable = false)
    private QualityAggregator aggregators;



    @Column(name = "rule_id", length = 64)
    private String ruleId;

    @Column(name = "rule_desc", nullable = false)
    private String ruleDesc;

    @Column(name = "value")
    private String value;

实体 2:
和 QualityAggregators。质量聚合器可以将另一个聚合器作为父级,而父级又可以有父级,依此类推。 因此,同一个 table.

中的子父关系
@Entity
@Table(name = "QUALITY_AGGREGATOR")
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@JsonNaming(PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy.class)
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class QualityAggregator {

    @Id
    @Column(name = "agg_id")
    private String aggId;

    @Column(name = "project_id")
    private String projectId;

    @Column(name = "job_id")
    private String jobId;

    @Column(name = "job_instance_id")
    private String jobInstanceId;

    @Column(name = "created_at")
    private Date timestamp;

    @Column(name = "agg_key")
    private String aggKey;

    @Column(name = "agg_value")
    private String aggValue;

    @Column(name = "level")
    private Integer level;

    @Column(name = "parent_agg_id")
    private String parentAggId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_agg_id", referencedColumnName = "agg_id",insertable = false,updatable = false)
    private QualityAggregator parent;

    @OneToMany(mappedBy = "parent",fetch = FetchType.EAGER)
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private Set<QualityAggregator> children;

注意:- 聚合器中的最后一个子项 table 映射到影响详细信息 table。

现在我想要做的是获取所有 InfluenceDetails,其中 agg_key 和 agg_value 在映射的子聚合器或其中任何父聚合器中都匹配提供的值。

这是我目前所拥有的。我不知道聚合器可能有多少级别,所以我采用默认值 5 并使 属性 可配置。但是必须有更好的方法。

public class InfluencerDetailsSpecification {

    @Value("{no.of.aggregators}")
    private Integer aggregators;

    @PostConstruct
    public void init(){
        setNoOfAggregators(aggregators);
    }

    private static Integer noOfAggregators = 5;

    /**
     * Creates a query for searching in aggregators and all its successive parents upto the no of levels specified(Defaults to 5)
     * @param request list of AggregatorsRequest to be searched
     * @return The prepared specification
     */
    public static Specification<InfluenceDetails> findByAggregators(List<AggregatorsRequest> request) {
        return (root, query, cb) -> {
            Join<InfluenceDetails, QualityAggregator> lastChild = root.join("aggregators");

            Function<AggregatorsRequest, Predicate> toPredicate = agg -> {
                List<Predicate> listOfOrPredicates = new ArrayList<>();
                Join<QualityAggregator, QualityAggregator> parent =  lastChild.join("parent",JoinType.LEFT);
                Predicate p0 = cb.and(cb.equal(lastChild.get("aggKey"), agg.getAggKey()), cb.equal(lastChild.get("aggValue"), agg.getAggValue()));
                listOfOrPredicates.add(p0);
                Integer aggsNos = Integer.valueOf(noOfAggregators);
                while(aggsNos-- > 2){
                    listOfOrPredicates.add(cb.and(cb.equal(parent.get("aggKey"), agg.getAggKey()), cb.equal(parent.get("aggValue"), agg.getAggValue())));
                    parent = parent.join("parent",JoinType.LEFT);
                }
                return cb.or(listOfOrPredicates.toArray(new Predicate[listOfOrPredicates.size()]));
            };
            return cb.and(cb.or(request.stream().map(toPredicate).toArray(Predicate[]::new)));
        };

    }

    public static Integer getNoOfAggregators() {
        return noOfAggregators;
    }

    public static void setNoOfAggregators(Integer noOfAggregators) {
        if(Objects.isNull(noOfAggregators))
            noOfAggregators = 5;

        InfluencerDetailsSpecification.noOfAggregators = noOfAggregators;
    }
}

这是递归的"one step"解决方案。我用 FluentJPA:

实现了它
public List<InfluenceDetails> getDetailsByAggregator(String aggKey,
                                                     String aggValue) {
    FluentQuery query = FluentJPA.SQL((InfluenceDetails details) -> {

        QualityAggregator relevantAgg = subQuery((QualityAggregator it,
                                                  QualityAggregator agg,
                                                  QualityAggregator child) -> {
            SELECT(agg);
            FROM(agg);
            WHERE(agg.getAggKey() == aggKey && agg.getAggValue() == aggValue);

            UNION_ALL();

            SELECT(child);
            FROM(child).JOIN(recurseOn(it)).ON(child.getParent() == it);
        });

        WITH(RECURSIVE(relevantAgg));

        // DISTINCT removes possible dups
        SELECT(DISTINCT(details));
        FROM(details).JOIN(relevantAgg).ON(details.getAggregators() == relevantAgg);
    });

    return query.createQuery(em, InfluenceDetails.class).getResultList();
}

这会产生以下结果 SQL:

WITH RECURSIVE q0  AS 
(SELECT t2.* 
FROM QUALITY_AGGREGATOR t2 
WHERE ((t2.agg_key = ?1) AND (t2.agg_value = ?2)) 
UNION ALL  
SELECT t3.* 
FROM QUALITY_AGGREGATOR t3  INNER JOIN q0 t1  ON (t3.parent_agg_id = t1.agg_id) )

SELECT DISTINCT t0.*  
FROM INFLUENCE_DETAILS t0  INNER JOIN q0  ON (t0.aggregator_id = q0.agg_id)

解释WITH RECURSIVE