【问题标题】: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当孩子和父母都使用 JPA Hibernate 存储在同一个表中时,在当前和所有连续的父母中搜索提供的值
【发布时间】:2020-01-09 21:22:10
【问题描述】:

我有两个实体:

使用聚合器映射为 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:
和质量聚合器。一个质量聚合器可以有另一个聚合器作为父级,它可以再次有一个父级,依此类推。 因此在同一个表中存在子父关系。

@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;

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

现在我要做的是获取所有 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;
    }
}

【问题讨论】:

  • 没有办法使用 Criteria API 有效地做到这一点。您需要改用 RDBMS 提供的分层查询功能(例如 WITH RECURSIVECONNECT BY
  • 是的,这是选项。我可以使用聚合器表中提供的值递归查询以查找叶节点,然后使用 InfluenceDetails 表中的 aggId 进行搜索。但这将涉及两个不同的步骤。这就是为什么我一直在寻找一种方法来一次性使用标准。如果没有其他选择,那么我会这样做。

标签: java hibernate spring-boot jpa criteria-api


【解决方案1】:

这是递归的“一步”解决方案。我用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的解释。

【讨论】:

  • 非常感谢。将研究 Fluent Jpa
猜你喜欢
  • 2021-10-17
  • 2021-02-16
  • 1970-01-01
  • 1970-01-01
  • 2012-10-16
  • 1970-01-01
  • 1970-01-01
  • 2017-08-16
  • 1970-01-01
相关资源
最近更新 更多