Query value of JoinColumn with JPQL - jpa

I have two JPA entities
public class Job {
#ManyToOne
#JoinColumn(name = "service")
public Service service;
#Column(name = "queue_time")
public Long queueTime;
#Column(name = "run_time")
public Long runTime;
}
public class Service {
#Id
#Column(name = "id")
public Long id;
#Column(name = "name")
public String name;
#Column(name = "host")
public String host;
}
Now I want to do some aggregation queries with JPQL:
SELECT job.service.id, AVG(job.queueTime), AVG(job.runTime) FROM Job job GROUP BY job.service.id
The resulting SQL query (I'm using a MySQL database) looks like this:
SELECT t0.id, AVG(t1.queueTime), AVG(t1.runTime) FROM Service t0, Job t1 WHERE (t0.service = t1.id) GROUP BY t0.id
As you can see, JPA translates my JPQL query to a SQL query with a join. This however slows down the query dramatically. The following SQL query executes ~6 time faster and returns the exact same result set:
SELECT t1.service, AVG(t1.queueTime), AVG(t1.runTime) FROM Job t1 GROUP BY t1.service
If I change the JPQL query to
SELECT job.service, AVG(job.queueTime), AVG(job.runTime) FROM Job job GROUP BY job.service
the resulting SQL query looks like this:
SELECT t0.id, t0.name, t0.host AVG(t1.queueTime), AVG(t1.runTime) FROM Service t0, Job t1 WHERE (t0.service = t1.id) GROUP BY t0.id, t0.name, t0.host
Is there a way to write the JPQL which only queries the job table without making a join to the service table?

This question solved the issue for me: How can I retrieve the foreign key from a JPA ManyToOne mapping without hitting the target table?
I took the second solution (b) Use read-only fields for the FKs)

Related

How to run Custom query on audit table hibernate envers

Trying to retreive records through custom query on audit table. I have Hibernate entity structure like below..,
#Audited
#Entity
public class TableA {
#EmbeddedId
private PrimaryKeyID primaryKeyID;
private Double price;
private Date modifiedOn;
private String status;
}
#Embeddable
public class PrimaryKeyID implements Serializable {
private Integer Id;
}
The query I am trying to replicate through AuditFactory is
SELECT * FROM table_A_aud where id = ? and status=? order by modifiedOn desc limit 3;
Tried Hibernate document on the Envers but I didnt find much info on that. Can someone kindly help me with this?
In order to issue a query against the audit tables, you would do the following:
List results = AuditReaderFactory.get( session )
.createQuery()
.forRevisionsOfEntity( TableA.class, true, false )
.add( AuditEntity.id().eq( entityId ) )
.add( AuditEntity.property( "status" ).eq( entityStatus ) )
.addOrder( AuditEntity.property( "modifiedOn" ).desc() )
.setMaxResults( 3 )
.getResultList();
In this query, we instruct the reader to get only entity instances (excluding delete markers) where the entity id is equal to entityId and the status is equal to entityStatus and ordering the results based on the modifiedOn column in descending order.
You'll notice that a lot of this Query API mimics that of the legacy Hibernate Criteria API.

Rewrite SQL query with JOINS in JPA

I have this SQL query for MariaDB.
select #ref:=id as id, unique_id, reference_id
from mytable
join (select #ref:=id from mytable WHERE unique_id = 55544)tmp
where reference_id=#ref
https://www.db-fiddle.com/f/jKJodfVfvw65aMaVDyFySd/0
How this query can be implemented in HQL query? I would like to use it in JPA?
(Answer largely re-written after comments below)
JPA doesn't have built-in support for hierarchical queries. The main option is a native query.
E.g. with this entity class:
#Entity
public class MyTable {
#Id
#GeneratedValue
private int id;
private int uniqueId;
#ManyToOne
private MyTable reference;
// ... getters and setters ...
}
The following is an example of a native hierachical SQL query (actually against MySQL, just in case):
Query query = entityManager.createNativeQuery(
"select #ref\\:=id as id, unique_id, reference_id\r\n" +
"from my_table\r\n" +
"join (select #ref\\:=?)tmp\r\n" +
"where reference_id=#ref",
MyTable.class);
query.setParameter(1, 1);
query.getResultList();
This was chasing down a chain of references successfully.
(Other alternatives)
There probably aren't too many other options that can do this as a single query. If scalability is less of a concern, adding a back reference would be a simple way to navigate the model:
#OneToMany(mappedBy = "reference")
private Set<MyTable> backReferences;
Those would then be straightforward to recursively navigate. Clearly the relation defaults to lazy loading, so would add little overhead until used.
With #df778899's MyTable in spring-data it could look like:
#Repository
public interface MyRepository extends ...
#Query("select #ref:=id as id, unique_id, reference_id "+
"from mytable join (select #ref:=id from mytable WHERE unique_id = :pUid) tmp "+
"where reference_id=#ref", //just copy paste the query, use :pUid instead of constant...
nativeQuery = true) // and this!
List<MyTable> myCustomHirachicalQuery(#Param("pUid") Integer uid/*String/Long/...*/);
...

JPQL query delete not accept a declared JOIN?

I'm trying to understand why the Hibernate not accepts this follow JPQL:
#Modifying
#Query("delete from Order order JOIN order.credit credit WHERE credit.id IN ?1")
void deleteWithListaIds(List<Long> ids);
The error that I receive is:
Caused by: java.lang.IllegalArgumentException: node to traverse cannot be null!
at org.hibernate.hql.internal.ast.util.NodeTraverser.traverseDepthFirst(NodeTraverser.java:46)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:284)
But accepts this:
#Modifying
#Query("delete from Order order WHERE order.credit.id IN ?1")
void deleteWithListaIds(List<Long> ids);
The entity Order (the entity Credit does not map the Orders):
#Entity
public class Order {
#Id
#Setter
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
#SequenceGenerator(name = SEQUENCE, sequenceName = SEQUENCE, allocationSize = 1)
#Column(name = "id", nullable = false)
private Long id;
#JoinColumn(name = "credit_id", foreignKey = #ForeignKey(name = "fk_order_credit"))
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Credit credit;
}
In select statements, the two approaches are accepted, but I don't understand why Hibernate have this limitation or if I'm doing something wrong in my DELETE Jpql. I would like to declare the JOIN in the query.
The only way that I know to resolve this problem in more complex queries is create a subselect:
delete from Order order WHERE order.id IN (
SELECT order.id FROM Order order
JOIN order.credit credit
WHERE credit.id in ?1)
Is this the right approach for more complex delete queries?
I'm using the Spring Jpa Repository in the code above and Spring Boot 1.5.10.RELEASE.
I don't understand why Hibernate have this limitation.
It is specified as such in the JPA Spec in section 4.10:
delete_statement ::= delete_clause [where_clause]
delete_clause ::= DELETE FROM entity_name [[AS] identification_variable]
So joins aren't allowed in delete statements.
Why this was decided this way is pure speculation on my side.
But the select_clause or delete_clause specify what the query operates on. While it is totally fine for a select statement to operate on a combination of multiple entities a join for a delete doesn't really make much sense.
It just forces you to specify which entity to delete.
The only way that I know to resolve this problem in more complex queries is to create a subselect:
Is this the right approach for more complex delete queries?
If you can't express it using simpler means then yes, this is the way to go.

hibernate isnt joining the table correctly

I have two tables, Schedule and UserSchedule. UserSchedule has columns schedule_id and user_id. What I want to do is something like this select s from schedule s join user_schedule us on s.id = us.schedule_id where us.user_id = ?. This works as intended if I run this query directly to db.
Schedule entity has userSchedule and all other necessary info
#Setter
#Getter
#JoinColumn(name = "id", referencedColumnName = "schedule_id")
#OneToOne
private UserSchedule userSchedule;
What's so weird is that I have #Query("select s from Schedule s join s.userSchedule") , and hibernate creates
select schedule0_.id as id1_10_, schedule0_.created_at as created_2_10_, schedule0_.end_date as end_date3_10_, schedule0_.end_time as end_time4_10_, schedule0_.frequency as frequenc5_10_, schedule0_.frequency_val as frequenc6_10_, schedule0_.is_active as is_activ7_10_, schedule0_.start_date as start_da8_10_, schedule0_.start_time as start_ti9_10_, schedule0_.updated_at as updated10_10_, schedule0_.description as descrip11_10_, schedule0_.lat as lat12_10_, schedule0_.lon as lon13_10_, schedule0_.project_id as project15_10_, schedule0_.title as title14_10_, (select v.status from voucher v where v.schedule_id = schedule0_.id) as formula1_ from schedule schedule0_ inner join user_schedule userschedu1_ on schedule0_.id=userschedu1_.id limit ?
The last part of the query on schedule0_.id=userschedu1_.id which should be I think on schedule0_.id=userschedu1_.schedule_id. Why tho? I can't seem to find any error.

How replace native order by clause on JPA equivalent?

I use JPA 2.0 criteria builder. I need get data from one table and sort them by column from other. This tables have relations OneToMany:
class Club{
#OneToMany(mappedBy = "club")
private List<Address> addresses;
...
}
class Address{
#JoinColumn(name = "club_id", referencedColumnName = "id")
#ManyToOne(fetch = FetchType.LAZY)
private Club club;
#Column(name = "type")
private Long type;
#Column(name = "full_address")
private String full_address;
...
}
May be several address of some type but I need only one row of this specific address.
I write native queries with subquery, but it's has problem because subquery doesn't use in order clause and in select clause in JPA 2.0.
select c.full_name from club c
ORDER BY (select a.full_address from address a WHERE c.id= a.club_id and a.type=1 LIMIT 1)
select c.full_name, (select a.full_address from address a WHERE a.type=1 AND c.id=a.club_id LIMIT 1) as full_address FROM club c
ORDER BY fullAddress;
How I can replace native order by clause on JPA equivalent?
Thanks!
This native query also resolve problem and it can replace by JPA query
select c.full_name, min(a.full_address) FROM club c LEFT JOIN address a on c.id = a.club_id
where a.id is null or a.type=1 or not exists(SELECT 1 from address aSub WHERE aSub .club_id=c.id AND aSub.type=1)
GROUP BY c.id, c.full_name ORDER BY min(a.full_address);
JPA equivalent
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<ClubItem> query = builder.createQuery(ClubItem.class);
Root<Club> root = query.from(Club.class);
Join<Club, Address> addressJoin = root.join(Club_.address, JoinType.LEFT);
query.select(builder.construct(ClubItem.class, root.get(Club_.id), root.get(Club_.fullName), builder.function("min", String.class, addressJoin.get(Address_.fullAddress))));
Subquery<Address> subquery = query.subquery(Address.class);
Root<Address> addressRoot = subquery.from(Address.class);
subquery.select(addressRoot);
subquery.where(
builder.and(
builder.equal(addressRoot.get(Address_.type), 1),
builder.equal(addressRoot.get(Address_.clubId), root.get(Club_.id))));
query.where(builder.or(builder.isNull(addressJoin), builder.equal(addressJoin.get(Address_.type), builder.literal(new Long(1))),
builder.not(builder.exists(subquery))));
query.groupBy(root.get(Club_.id), root.get(Club_.fullName))
Order order = builder.asc(builder.function("min", String.class, addressJoin.get(Address_.fullAddress)));
query.orderBy(order);
TypedQuery<ClubItem> contentQuery = em.createQuery(query);
It's not terribly elegant, but it gets the job done...
Make your "Club" class implement Comparable. Put the order-by logic into the Comparable. Then use Collections.sort(unsortedList) to get the list into sorted form. There's also a Collections.sort(unsortedList, Comparable) method which could be useful, especially if you are doing a bunch of similar methods that just vary on order-by.