The problem that jpa custom query does not take effect - java

I am designing a 'like' function for a blog system, and I want to judge whether the likes have been added/applied according to the user's login status when querying the content of the homepage.
So I have created a SQL statement to implement the left join to the query to achieve this function. I used the JPA #Query jpql method to customize the query, but the effect is different from what I expected.
When looking at the statement, I found that the jpql statement is indeed executed once, but it is an associated query to another/subsequent jpa query after the initial query was executed, so I think it may be overwritten.
I am using jpa for the first time and they may be thing that I do not fully understand, please feel free to ask any further questions to help me and thank you.
This is my expected sql statement:
SELECT a.*,l.*FROM article a LEFT JOIN user_like_record l ON a.id=l.target_id AND l.target_type=0 AND l.user_id=17
This is my current code:
#Query(value = "SELECT\n" +
"\tarticle\n" +
"FROM\n" +
"\tArticle article LEFT JOIN article.userLikeRecord ON article.userLikeRecord.targetType = 0\n" +
"\t AND article.userLikeRecord.userId = :userId")
Page<Article> findAllLikeStatusb(Long userId, Pageable pageable);
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id", referencedColumnName = "target_id", insertable = false, updatable = false)
private UserLikeRecord userLikeRecord;
This is the sql statement generated by the last query of jpa:
Hibernate:
select
user0_.id as id1_9_,
user0_.create_time as create_t2_9_,
user0_.delete_time as delete_t3_9_,
user0_.update_time as update_t4_9_,
user0_.email as email5_9_,
user0_.mobile as mobile6_9_,
user0_.nick_name as nick_nam7_9_,
user0_.openid as openid8_9_,
user0_.receive_like_counts as receive_9_9_,
user0_.unify_uid as unify_u10_9_,
user0_.user_birthday as user_bi11_9_,
user0_.wx_profile as wx_prof12_9_
from
user user0_
where
(
user0_.delete_time is null
)
and user0_.openid=?
Hibernate:
SELECT
a.*,
l.*
FROM
article a
LEFT JOIN
user_like_record l
ON a.id = l.target_id
AND l.target_type=0
AND l.user_id =?
order by
a.create_time desc limit ?
The following sql is executed multiple times
Hibernate:
select
userlikere0_.target_id as target_i1_10_0_,
userlikere0_.create_time as create_t2_10_0_,
userlikere0_.delete_time as delete_t3_10_0_,
userlikere0_.update_time as update_t4_10_0_,
userlikere0_.id as id5_10_0_,
userlikere0_.like_status as like_sta6_10_0_,
userlikere0_.target_type as target_t7_10_0_,
userlikere0_.user_id as user_id8_10_0_
from
user_like_record userlikere0_
where
userlikere0_.target_id=?
and (
userlikere0_.delete_time is null
)

Related

Convert SQL Query to Criteria Query in Spring Boot

I'm relatively new to Spring JPA CriteriaQuery. Im trying to convert my old native query in my program to criteria query and haven't been successful on join query for multiple table with conditions. I need help converting native SQL query into Criteria Query for these query below :
select * from student s inner join (
select distinct on (student_id) * from class where status = 'Active' order by
student_id,date_register desc) c on s.id = c.user_id
inner join teacher t on t.subject_id = c.subject_id
where t.status = 'Active' and s.status='Active' order by s.name desc
Update :
Below code is as far as I can go cause I dont really know much. Am i in the right direction? I'm opting for Expression because i dont know how to use Join.
CriteriaQuery<Student> query = cb.createQuery(Student.class);
Root<Student> sRoot= query.from(Student.class);
query.select(sRoot);
Subquery<Integer> subquery = query.subquery(Integer.class);
Root<Class> cRoot= subquery.from(CLass.class);
Expression<Integer> max = cb.max(cRoot.get("dateRegister"));
subquery.select(max);
subquery.groupBy(cRoot.get("student"));
query.where(
cb.and(
cb.in(cRoot.get("dateRegister")).value(subquery)
)
);
Thanks in advance!

How to rewrite subquery in ORDER BY clause in JPA CriteriaQuery

I'm trying to write an SQL query using CriteriaQuery, but I'm having a hard time doing so. This query basically gets a list of shipments and sorts them by their authorization date. This authorization date is represented as the date attribute of the first record in the status transition messages table with an initial status of 3 and a final status of 4. This is my query:
SELECT s.id
FROM shipment s
ORDER BY (SELECT min(stm.date)
FROM status_transition_message stm
WHERE stm.initial_status = 1 AND stm.final_status = 3 AND stm.shipment_id = s.id) desc;
I've tried multiple different solutions, but none have worked so far.
My current iteration is as follows:
private void sortByAuthDate(Root<ShipmentTbl> root, CriteriaQuery<?> query, CriteriaBuilder builder, ListSort sort) {
Subquery<Timestamp> authDateQuery = query.subquery(Timestamp.class);
Root<StatusTransitionMessageTbl> stmRoot = authDateQuery.from(StatusTransitionMessageTbl.class);
Predicate shipmentId = builder.equal(stmRoot.<ShipmentTbl>get("shipment").<String>get("id"), root.<String>get("id"));
Predicate initialStatus = builder.equal(stmRoot.<Integer>get("initialStatus"), 3);
Predicate finalStatus = builder.equal(stmRoot.<Integer>get("finalStatus"), 4);
// returns the authorization date for each queried shipment
authDateQuery.select(builder.least(stmRoot.<Timestamp>get("date")))
.where(builder.and(shipmentId, initialStatus, finalStatus));
Expression<Timestamp> authDate = authDateQuery.getSelection();
Order o = sort.getSortDirection() == ListSort.SortDirection.ASC ? builder.asc(authDate) : builder.desc(authDate);
query.multiselect(authDate).orderBy(o);
}
The problem with this solution is that the SQL query generated by the CriteriaQuery does not support subqueries in the ORDER BY clause, causing a parsing exception.
My CriteriaQuery-fu is not good enough to help you with that part, but you could rewrite your SQL query to this:
SELECT s.id
FROM shipment s
LEFT JOIN status_transition_message stm
ON stm.initial_status = 1 AND stm.final_status = 3 AND stm.shipment_id = s.id
GROUP BY s.id
ORDER BY min(stm.date) DESC;
To me, this quite likely seems to be a faster solution anyway than running a correlated subquery in the ORDER BY clause, especially on RDBMS with less sophisticated optimisers.
So I attempted to follow #Lukas Eder solution and reached this solution:
private void sortByAuthDate(Root<ShipmentTbl> root, CriteriaQuery<?> query, CriteriaBuilder builder, ShipmentListSort sort) {
Join<ShipmentTbl, StatusTransitionMessageTbl> shipmentStatuses = root.join("shipmentStatus", JoinType.LEFT);
Predicate initialStatus = builder.equal(shipmentStatuses.<Integer>get("initialStatus"), 1);
Predicate finalStatus = builder.equal(shipmentStatuses.<Integer>get("finalStatus"), 3);
Expression<Timestamp> authDate = builder.least(shipmentStatuses.<Timestamp>get("date"));
Order o = sort.getSortDirection() == ShipmentListSort.SortDirection.ASC ? builder.asc(authDate) : builder.desc(authDate);
shipmentStatuses.on(builder.and(initialStatus, finalStatus));
query.multiselect(authDate).groupBy(root.<String>get("id")).orderBy(o);
}
}
But now it's throwing this exception:
ERROR o.h.e.jdbc.spi.SqlExceptionHelper - ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
This happens because the query is only going to get distinct shipments later on and it's asking for the sorting column also appear in the select. The problem is I don't know how to force CriteriaQuery to keep that column in the SELECT statement. It automatically only puts in the ORDER BY.
Here's the JPQL query it's executing in my test:
select
distinct generatedAlias0
from
ShipmentTbl as generatedAlias0
left join
generatedAlias0.shipmentStatus as generatedAlias1 with ( generatedAlias1.initialStatus=:param0 )
and (
generatedAlias1.finalStatus=:param1
)
where
lower(generatedAlias0.shipmentName) like :param2
group by
generatedAlias0.id
order by
min(generatedAlias1.date) desc
and the generated SQL query:
select
distinct shipmenttb0_.id as id1_13_,
shipmenttb0_.archived_date as archived2_13_,
shipmenttb0_.auth_code as auth_cod3_13_,
shipmenttb0_.authorization_date as authoriz4_13_,
shipmenttb0_.booked_in_by_user as booked_i5_13_,
shipmenttb0_.business_channel as business6_13_,
shipmenttb0_.courier as courier7_13_,
shipmenttb0_.courier_amount as courier_8_13_,
shipmenttb0_.courier_currency as courier_9_13_,
shipmenttb0_.ship_to as ship_to39_13_,
shipmenttb0_.estimated_shipment_date as estimat10_13_,
shipmenttb0_.last_updated_date as last_up11_13_,
shipmenttb0_.measurement_unit as measure12_13_,
shipmenttb0_.original_submitted_date as origina13_13_,
shipmenttb0_.packaging_type as packagi14_13_,
shipmenttb0_.placeholder_message as placeho15_13_,
shipmenttb0_.scheduled_period_of_day as schedul16_13_,
shipmenttb0_.scheduled_shipment_date as schedul17_13_,
shipmenttb0_.ship_from as ship_fr40_13_,
shipmenttb0_.ship_origin as ship_or41_13_,
shipmenttb0_.shipment_name as shipmen18_13_,
shipmenttb0_.status as status19_13_,
shipmenttb0_.submitted_date as submitt20_13_,
shipmenttb0_.supplier_contact_email as supplie21_13_,
shipmenttb0_.supplier_contact_name as supplie22_13_,
shipmenttb0_.supplier_contact_phone_number as supplie23_13_,
shipmenttb0_.supplier_email as supplie24_13_,
shipmenttb0_.supplier_secondary_contact_email as supplie25_13_,
shipmenttb0_.supplier_secondary_contact_name as supplie26_13_,
shipmenttb0_.supplier_secondary_contact_phone_number as supplie27_13_,
shipmenttb0_.tenant as tenant28_13_,
shipmenttb0_.total_received_boxes as total_r29_13_,
shipmenttb0_.total_units as total_u30_13_,
shipmenttb0_.total_value as total_v31_13_,
shipmenttb0_.total_volume as total_v32_13_,
shipmenttb0_.total_weight as total_w33_13_,
shipmenttb0_.tracking_number as trackin34_13_,
shipmenttb0_.tt_note as tt_note35_13_,
shipmenttb0_.tt_priority as tt_prio36_13_,
shipmenttb0_.updated_by_user as updated37_13_,
shipmenttb0_.weight_unit as weight_38_13_
from
shipment shipmenttb0_
left outer join
status_transition_message shipmentst1_
on shipmenttb0_.id=shipmentst1_.shipment_id
and (
shipmentst1_.initial_status=?
and shipmentst1_.final_status=?
)
where
lower(shipmenttb0_.shipment_name) like ?
group by
shipmenttb0_.id
order by
min(shipmentst1_.date) desc limit ?

JPA Query: join a subquery with grouping condition

I have an entity that represents a change event for a specific object. Something like:
#Entity
public class Event {
#Id
private String eventId;
private String objectId;
private Instant creationDate;
// other fields, getters, setters
}
There might be several event objects for a specific objectId.
Now I need to query all latest events for a each objectId (those that have max creationDate groping by objectId) .
If it was pure SQL I would write the following query:
SELECT event.*
FROM
event event
JOIN (
SELECT
e.object_id object_id,
MAX(e.creation_date) last_date
FROM event e
GROUP BY e.object_id
) latest_event
ON latest_event.object_id = event.object_id
AND event.creation_date = latest_event.last_date
But the similar join unfortunately doesn't work in JPA query.
Question: How to join a subquery in a JPA query?
Using a native query is not an option in my case, because I use Spring Data JPA repository with pagination functionality which doesn't work for native queries.
#Query(
value = "SELECT e FROM Event e " +
"WHERE e.creationDate = " +
"(SELECT max(e2.creationDate) FROM Event e2 " +
"WHERE e2.objectId = e.objectId)"
)
SELECT *
FROM Event
NATURAL JOIN
(SELECT object_id, MAX(creation_date) AS creation_date
FROM Event
GROUP BY object_id) groupedEvent
if two equal max creation_date be in the same object_id
SELECT ev.*
FROM Event ev
INNER JOIN
(SELECT max(id) AS id
FROM Event e
INNER JOIN
(SELECT object_id, MAX(creation_date) AS last_date
FROM Event GROUP BY object_id
) groupedEvent
ON e.object_id = groupedEvent.object_id
AND e.creation_date = groupedEvent.last_date
GROUP BY e.object_id) dist
ON ev.id = dist.id;
```

How to determine if two lists share any objects with Hibernate

I have this example SQL query that I created that tries to find all REPORTs that have an associated REPORT_PERMISSION object with one of the USER_GROUPs that the current user also has. So there are many REPORT_PERMISSION objects that tie a report to a group, and a user can have many groups, just one of those have to match up to allow the user to view the report. I'm just not sure how to write this in HQL
SELECT * FROM REPORT r
JOIN REPORT_PERMISSION rp
on r.id = rp.report_id and rp.user_group_id in
(SELECT l.user_group_id FROM USER_GROUP_LINK l where l.user_id = 2)
where r.type = 'GENERAL';
it should be something like :
Query reportQuery = entityManager.createQuery
("select distinct rep from Report rep
join rep.reportPermissions per
join per.userGroups gr
join gr.users u
where u.id = :userId and rep.type = 'GENERAL'");
reportQuery.setParameter("userId" , user.getId());
example mapping (names that are used in hql , and fileds name in mappings ) :
rep.reportPermissions -- Collection < ReportPermission> reportPermissions in Report;
per.userGroups -- Collection < UserGroup> userGroups in ReportPermission;
gr.users -- Collection < User> users in UserGroup;
u.id -- #Id Long id; in user class

How do I port query with join across multiple tables from JDOQL to HQL

I am porting an application for KodoJDO to Hibernate.
I have a query that goes across 4 tables in the db, and 3 objects in the java code.
In English the query is Find the users that have entitlements in system X.
my JDOQL where clause called on the User object was
where entitlements.contains(ent) && (upper( ent.system.id ) = 'EVPN')
some sql that does the query is:
select unique(u.id)
from USER u, USERENTITLEMENT ue, ENTITLEMENT e, SYSTEM s
where u.id = ue.userid
and ue.entitlementid = e.id
and e.systemid = s.id
and s.id = 'evpn'
My best guess for HQL gives me an exception
org.hibernate.hql.ast.QuerySyntaxException: unexpected AST node: ( [select user from com.ebig.entity.User as user, com.ebig.entity.Entitlement as ent, com.ebig.entity.System as sys where entitlements.contains(ent) and ent.system = sys and sys.id = 'evpn']
the db is structured like this:
User
id
UserEntitlement
userid
entitlementid
Entitlement
id
systemid
System
id
the java code is structured as below:
class User
{
String id;
Set<Entitlement> entitlements;
}
class Entitlement
{
String id;
System system;
}
class System
{
String id;
}
Update My final query that works
hqlQuery = "select distinct user from User as user "+
"inner join user.entitlements as entitlement inner join entitlement.system as system "+
"where system.id = 'evpn' AND mod(user.flags, 2) = 0 AND source = 1";
Yes I know I should use parameters, but I have a great many problems to solve, and will post pone that code for another day.
Another variation with an implicit inner join for entitlement to system
hqlQuery = "select distinct user from User as user "+
"inner join user.entitlements as entitlement "+
"where entitlement.system.id = 'evpn' AND mod(user.flags, 2) = 0 AND source = 1";
You should use joins :
select distinct u.id from User u
inner join u.entitlements as entitlement
inner join entitlement.system as system
where system.id = :evpn
where :evpn is a named parameter that you have to bind.
You must think in terms of objects and relationships between objects when doing HQL, and not in terms of tables, foreign keys and join tables.

Categories

Resources