将JPQL查询转换为Criteria查询

huangapple go评论51阅读模式
英文:

Converting JPQL query to Criteria Query

问题

以下是你要翻译的内容:

Working SQL:

SELECT *
FROM   tag
       JOIN (SELECT tag_id,
                    Count (gift_certificate_tag.gift_certificate_id) AS
                    cert_count
             FROM   gift_certificate_tag
                    JOIN orders
                      ON gift_certificate_tag.gift_certificate_id =
                         orders.gift_certificate_id
                    JOIN (SELECT user_id,
                                 Sum (order_cost) AS all_orders_cost
                          FROM   orders
                          GROUP  BY user_id
                          ORDER  BY all_orders_cost DESC
                          LIMIT  1) AS orders_sum
                      ON orders_sum.user_id = orders.user_id
             GROUP  BY tag_id
             ORDER  BY cert_count DESC
             LIMIT  1) AS result
             ON result.tag_id = tag.id

Working JPQL:

SELECT new Tag(t.id, t.name)
            FROM GiftCertificate gc
            JOIN gc.tags t
            JOIN Order o ON o.giftCertificate = gc
            JOIN o.user u
            WHERE u = (
                  SELECT ou
                  FROM Order o2
                  JOIN o2.user ou
                  GROUP BY ou
                  HAVING SUM(o2.orderCost) = (
                        SELECT MAX(totalOrderCost)
                        FROM (
                              SELECT SUM(o3.orderCost) AS totalOrderCost
                              FROM Order o3
                              GROUP BY o3.user
                        ) subquery
                  )
            )
            GROUP BY t.id, t.name
            ORDER BY COUNT(gc) DESC

Not working Criteria Query:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tag> cq = cb.createQuery(Tag.class);
Root<Order> order = cq.from(Order.class);
Join<Order, GiftCertificate> giftCertificate = order.join("giftCertificate");
Join<GiftCertificate, Tag> tag = giftCertificate.join("tags");
Join<Order, User> user = order.join("user");

//sub-query to find user's orders sums
Subquery<BigDecimal> querySumOfOrders = cq.subquery(BigDecimal.class);
Root<Order> subOrderSumOfOrders = querySumOfOrders.from(Order.class);
Join<Order, User> subUserSumOfOrders = subOrderSumOfOrders.join("user");
querySumOfOrders.select(cb.sum(subOrderSumOfOrders.get("orderCost")))
                .groupBy(subUserSumOfOrders);

//sub-query to find max cost of the user's orders sum
Subquery<BigDecimal> queryMaxOrdersSum = cq.subquery(BigDecimal.class);
queryMaxOrdersSum.from(Order.class);
queryMaxOrdersSum.select(cb.max(querySumOfOrders));

//sub-query to find user with most cost of all orders
Subquery<User> queryMostSpentUser = cq.subquery(User.class);
Root<Order> subOrderMostSpentUser = queryMostSpentUser.from(Order.class);
Join<Order, User> subUserMostSpentUser = subOrderMostSpentUser.join("user");
queryMostSpentUser.select(subUserMostSpentUser)
                  .having(cb.equal(cb.sum(subOrderMostSpentUser.get("orderCost")), cb.max(queryMaxOrdersSum)))
                  .groupBy(subUserMostSpentUser);
//main query
cq.select(tag)
  .where(cb.equal(user, queryMostSpentUser))
  .orderBy(cb.desc(cb.count(giftCertificate)))
  .groupBy(tag);
List<Tag> tagList = entityManager.createQuery(cq)
                                 .setMaxResults(1)
                                 .getResultList();

The SQL generated by Hibernate from criteria query:

select
    t1_1.id,
    t1_1.name 
from
    public.orders o1_0 
join
    public.gift_certificate g1_0 
        on g1_0.id=o1_0.gift_certificate_id 
join
    (public.gift_certificate_tag t1_0 
join
    public.tag t1_1 
        on t1_1.id=t1_0.tag_id) 
            on g1_0.id=t1_0.gift_certificate_id 
    join
        public.users u1_0 
            on u1_0.id=o1_0.user_id 
    where
        u1_0.id=(
            select
                u2_0.id 
            from
                public.orders o2_0 
            join
                public.users u2_0 
                    on u2_0.id=o2_0.user_id 
            group by
                1 
            having
                sum(o2_0.order_cost)=(
                    select
                        max((select
                            sum(o4_0.order_cost) 
                        from
                            public.orders o4_0 
                        join
                            public.users u3_0 
                                on u3_0.id=o4_0.user_id 
                        group by
                            o4_0.user_id)) 
                    from
                        public.orders o3_0)
                ) 
            group by
                1,
                2 
            order by
                count(o1_0.gift_certificate_id) desc fetch first ? rows only

The problem is with the following SQL block:

select max((select
                            sum(o4_0.order_cost) 
                        from
                            public.orders o4_0 
                        join
                            public.users u3_0 
                                on u3_0.id=o4_0.user_id 
                        group by
                            o4_0.user_id))

this causes org.postgresql.util.PSQLException: ERROR: more than one row returned by a subquery used as an expression

The question is how to make criteria query instead generate SQL same as generated in case of JPQL query:

                    select max(subquery1_0.totalOrderCost) 
                    from
                        (select
                            sum(o3_0.order_cost) 
                        from
                            public.orders o3_0 
                        group by
                            o3_0.user_id) subquery1_0(totalOrderCost)
英文:

In order to learn JPA I'm trying to explore various ways to execute queries. I have native SQL and JPQL queries which work as expected. Now I'm trying to achieve the same by Criteria Query approach.
My entities:

@Entity
public class GiftCertificate {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
@Column(precision = 10, scale = 2)
private BigDecimal price;
private Short duration;
private LocalDateTime createDate;
private LocalDateTime lastUpdateDate;
@ManyToMany
@JoinTable(
name = &quot;gift_certificate_tag&quot;,
joinColumns = @JoinColumn(name = &quot;gift_certificate_id&quot;),
inverseJoinColumns = @JoinColumn(name = &quot;tag_id&quot;)
)
private Set&lt;Tag&gt; tags = new HashSet&lt;&gt;();
public Set&lt;Tag&gt; getTags() {
return Collections.unmodifiableSet(tags);
}
}
@Entity
@Table(name = &quot;ORDERS&quot;)
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private User user;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private GiftCertificate giftCertificate;
private LocalDateTime orderDate;
private BigDecimal orderCost;
}
@Entity
@Table(name = &quot;tag&quot;, schema = &quot;public&quot;)
public class Tag {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//@Basic(optional = false)
//@NotNull
@Column(nullable = false)
private String name;
}
@Entity
@Table(name = &quot;users&quot;, schema = &quot;public&quot;)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String userName;
}

The task is to get the most widely used tag of a user with the highest cost of all orders

Working SQL:

SELECT *
FROM   tag
JOIN (SELECT tag_id,
Count (gift_certificate_tag.gift_certificate_id) AS
cert_count
FROM   gift_certificate_tag
JOIN orders
ON gift_certificate_tag.gift_certificate_id =
orders.gift_certificate_id
JOIN (SELECT user_id,
Sum (order_cost) AS all_orders_cost
FROM   orders
GROUP  BY user_id
ORDER  BY all_orders_cost DESC
LIMIT  1) AS orders_sum
ON orders_sum.user_id = orders.user_id
GROUP  BY tag_id
ORDER  BY cert_count DESC
LIMIT  1) AS result
ON result.tag_id = tag.id

Working JPQL:

SELECT new Tag(t.id, t.name)
FROM GiftCertificate gc
JOIN gc.tags t
JOIN Order o ON o.giftCertificate = gc
JOIN o.user u
WHERE u = (
SELECT ou
FROM Order o2
JOIN o2.user ou
GROUP BY ou
HAVING SUM(o2.orderCost) = (
SELECT MAX(totalOrderCost)
FROM (
SELECT SUM(o3.orderCost) AS totalOrderCost
FROM Order o3
GROUP BY o3.user
) subquery
)
)
GROUP BY t.id, t.name
ORDER BY COUNT(gc) DESC

Not working Criteria Query:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery&lt;Tag&gt; cq = cb.createQuery(Tag.class);
Root&lt;Order&gt; order = cq.from(Order.class);
Join&lt;Order, GiftCertificate&gt; giftCertificate = order.join(&quot;giftCertificate&quot;);
Join&lt;GiftCertificate, Tag&gt; tag = giftCertificate.join(&quot;tags&quot;);
Join&lt;Order, User&gt; user = order.join(&quot;user&quot;);
//sub-query to find user&#39;s orders sums
Subquery&lt;BigDecimal&gt; querySumOfOrders = cq.subquery(BigDecimal.class);
Root&lt;Order&gt; subOrderSumOfOrders = querySumOfOrders.from(Order.class);
Join&lt;Order, User&gt; subUserSumOfOrders = subOrderSumOfOrders.join(&quot;user&quot;);
querySumOfOrders.select(cb.sum(subOrderSumOfOrders.get(&quot;orderCost&quot;)))
.groupBy(subUserSumOfOrders);
//sub-query to find max cost of the user&#39;s orders sum
Subquery&lt;BigDecimal&gt; queryMaxOrdersSum = cq.subquery(BigDecimal.class);
queryMaxOrdersSum.from(Order.class);
queryMaxOrdersSum.select(cb.max(querySumOfOrders));
//sub-query to find user with most cost of all orders
Subquery&lt;User&gt; queryMostSpentUser = cq.subquery(User.class);
Root&lt;Order&gt; subOrderMostSpentUser = queryMostSpentUser.from(Order.class);
Join&lt;Order, User&gt; subUserMostSpentUser = subOrderMostSpentUser.join(&quot;user&quot;);
queryMostSpentUser.select(subUserMostSpentUser)
.having(cb.equal(cb.sum(subOrderMostSpentUser.get(&quot;orderCost&quot;)), cb.max(queryMaxOrdersSum)))
.groupBy(subUserMostSpentUser);
//main query
cq.select(tag)
.where(cb.equal(user, queryMostSpentUser))
.orderBy(cb.desc(cb.count(giftCertificate)))
.groupBy(tag);
List&lt;Tag&gt; tagList = entityManager.createQuery(cq)
.setMaxResults(1)
.getResultList();

The SQL generated by Hibernate from criteria query:

select
t1_1.id,
t1_1.name 
from
public.orders o1_0 
join
public.gift_certificate g1_0 
on g1_0.id=o1_0.gift_certificate_id 
join
(public.gift_certificate_tag t1_0 
join
public.tag t1_1 
on t1_1.id=t1_0.tag_id) 
on g1_0.id=t1_0.gift_certificate_id 
join
public.users u1_0 
on u1_0.id=o1_0.user_id 
where
u1_0.id=(
select
u2_0.id 
from
public.orders o2_0 
join
public.users u2_0 
on u2_0.id=o2_0.user_id 
group by
1 
having
sum(o2_0.order_cost)=(
select
max((select
sum(o4_0.order_cost) 
from
public.orders o4_0 
join
public.users u3_0 
on u3_0.id=o4_0.user_id 
group by
o4_0.user_id)) 
from
public.orders o3_0)
) 
group by
1,
2 
order by
count(o1_0.gift_certificate_id) desc fetch first ? rows only

The problem is with the following SQL block:

                select max((select
sum(o4_0.order_cost) 
from
public.orders o4_0 
join
public.users u3_0 
on u3_0.id=o4_0.user_id 
group by
o4_0.user_id))

this causes org.postgresql.util.PSQLException: ERROR: more than one row returned by a subquery used as an expression

The question is how to make criteria query instead generate SQL same as generated in case of JPQL query:

                        select max(subquery1_0.totalOrderCost) 
from
(select
sum(o3_0.order_cost) 
from
public.orders o3_0 
group by
o3_0.user_id) subquery1_0(totalOrderCost)

答案1

得分: 1

以下是翻译好的代码部分:

"The &quot;JPQL&quot; (actually HQL) can only work with Hibernate 6+, because before that, Hibernate did not support subqueries in the from clause. To use that feature from JPA Criteria, you have to use the Hibernate JPA Criteria extensions.

HibernateCriteriaBuilder cb = entityManager.unwrap(Session.class).getCriteriaBuilder();
JpaCriteriaQuery&lt;Tag&gt; cq = cb.createQuery(Tag.class);
JpaRoot&lt;Order&gt; order = cq.from(Order.class);
JpaJoin&lt;Order, GiftCertificate&gt; giftCertificate = order.join(&quot;giftCertificate&quot;);
JpaJoin&lt;GiftCertificate, Tag&gt; tag = giftCertificate.join(&quot;tags&quot;);
JpaJoin&lt;Order, User&gt; user = order.join(&quot;user&quot;);

//sub-query to find user&#39;s orders sums
JpaSubquery&lt;Tuple&gt; querySumOfOrders = cq.subquery(Tuple.class);
JpaRoot&lt;Order&gt; subOrderSumOfOrders = querySumOfOrders.from(Order.class);
querySumOfOrders.multiselect(cb.sum(subOrderSumOfOrders.get(&quot;orderCost&quot;)).alias(&quot;theSum&quot;))
                .groupBy(subOrderSumOfOrders.get(&quot;user&quot;));

//sub-query to find max cost of the user&#39;s orders sum
JpaSubquery&lt;BigDecimal&gt; queryMaxOrdersSum = cq.subquery(BigDecimal.class);
JpaRoot&lt;Tuple&gt; queryMaxOrders = queryMaxOrdersSum.from(querySumOfOrders);
queryMaxOrdersSum.select(cb.max(queryMaxOrders.get(&quot;theSum&quot;)));

//sub-query to find user with most cost of all orders
JpaSubquery&lt;User&gt; queryMostSpentUser = cq.subquery(User.class);
JpaRoot&lt;Order&gt; subOrderMostSpentUser = queryMostSpentUser.from(Order.class);
JpaJoin&lt;Order, User&gt; subUserMostSpentUser = subOrderMostSpentUser.join(&quot;user&quot;);
queryMostSpentUser.select(subUserMostSpentUser)
                  .having(cb.equal(cb.sum(subOrderMostSpentUser.get(&quot;orderCost&quot;)), cb.max(queryMaxOrdersSum)))
                  .groupBy(subUserMostSpentUser);
//main query
cq.select(tag)
  .where(cb.equal(user, queryMostSpentUser))
  .orderBy(cb.desc(cb.count(giftCertificate)))
  .groupBy(tag);
List&lt;Tag&gt; tagList = entityManager.createQuery(cq)
                                 .setMaxResults(1)
                                 .getResultList();

希望这对你有所帮助。如果有任何其他问题,请随时提出。

英文:

The "JPQL" (actually HQL) can only work with Hibernate 6+, because before that, Hibernate did not support subqueries in the from clause. To use that feature from JPA Criteria, you have to use the Hibernate JPA Criteria extensions.

    HibernateCriteriaBuilder cb = entityManager.unwrap(Session.class).getCriteriaBuilder();
JpaCriteriaQuery&lt;Tag&gt; cq = cb.createQuery(Tag.class);
JpaRoot&lt;Order&gt; order = cq.from(Order.class);
JpaJoin&lt;Order, GiftCertificate&gt; giftCertificate = order.join(&quot;giftCertificate&quot;);
JpaJoin&lt;GiftCertificate, Tag&gt; tag = giftCertificate.join(&quot;tags&quot;);
JpaJoin&lt;Order, User&gt; user = order.join(&quot;user&quot;);
//sub-query to find user&#39;s orders sums
JpaSubquery&lt;Tuple&gt; querySumOfOrders = cq.subquery(Tuple.class);
JpaRoot&lt;Order&gt; subOrderSumOfOrders = querySumOfOrders.from(Order.class);
querySumOfOrders.multiselect(cb.sum(subOrderSumOfOrders.get(&quot;orderCost&quot;)).alias(&quot;theSum&quot;))
.groupBy(subOrderSumOfOrders.get(&quot;user&quot;));
//sub-query to find max cost of the user&#39;s orders sum
JpaSubquery&lt;BigDecimal&gt; queryMaxOrdersSum = cq.subquery(BigDecimal.class);
JpaRoot&lt;Tuple&gt; queryMaxOrders = queryMaxOrdersSum.from(querySumOfOrders);
queryMaxOrdersSum.select(cb.max(queryMaxOrders.get(&quot;theSum&quot;)));
//sub-query to find user with most cost of all orders
JpaSubquery&lt;User&gt; queryMostSpentUser = cq.subquery(User.class);
JpaRoot&lt;Order&gt; subOrderMostSpentUser = queryMostSpentUser.from(Order.class);
JpaJoin&lt;Order, User&gt; subUserMostSpentUser = subOrderMostSpentUser.join(&quot;user&quot;);
queryMostSpentUser.select(subUserMostSpentUser)
.having(cb.equal(cb.sum(subOrderMostSpentUser.get(&quot;orderCost&quot;)), cb.max(queryMaxOrdersSum)))
.groupBy(subUserMostSpentUser);
//main query
cq.select(tag)
.where(cb.equal(user, queryMostSpentUser))
.orderBy(cb.desc(cb.count(giftCertificate)))
.groupBy(tag);
List&lt;Tag&gt; tagList = entityManager.createQuery(cq)
.setMaxResults(1)
.getResultList();

huangapple
  • 本文由 发表于 2023年3月1日 13:29:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/75599897.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定