Rewrite SQL query with JOINS in JPA - 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/...*/);
...

Related

JPA Query Left Join with A non-declared relation

I need to query an Entity with a left join with another entity. But I can only declare the ManyToOne relation on the left table:
First table:
#Entity
public class ShipyardModel {
private Long id
....etc
}
Second table (LEFT one):
#Entity
public class Boat{
private Long id;
...
#ManyToOne
#JoinColumn(name = "shipyard_model_id")
private ShipyardModel shipyardModel;
}
Because of serialization reasons I can't declare the #OneToMany relation in the first entity. But they are related, because in the Boat table, i have the shipyard_mode_id. So I created a DTO to query this guy for me and created a JPA query:
Query(" SELECT new br.com.easymarine.dto.ShipyardModelDTO" +
"(sm.id, sm.name, sm.commercialLength, sm.length, sm.width, sm.height, sm.beam, sm.weightWithoutMotor, sm.weightWithMotor, sm.modelYear, sm.shipType,s.id, s.name,count(b.id)) " +
"from ShipyardModel sm " +
"join sm.shipyard s " +
"left join Boat b on b.shipyardModel = sm GROUP BY sm.id")
List<ShipyardModelDTO> findAllShipyardModelDTO();
The Spring is giving me
Path expected for join!
error
Is it possible to execute this query the way it is?

How can I write an EXISTS predicate on a collection attribute in Criteria API?

I have these classes:
#Entity
public class Customer {
#Id long id;
String name;
#OneToMany List<Customer> related;
}
and I'm using this JPQL query:
select c from Customer c where c.name = 'ACME'
or exists( select 1 from c.related r where r.name = 'ACME' )
How can I write the same query with the Criteria API? I need to use exists with a subquery, like the JPQL, but I don't know how to create a subquery from a collection attribute in the Criteria API.
Something like this would give EXISTS (subquery)
Subquery<Long> sq = cq.subquery(Long.class);
Root<Customer> customerSub = sq.correlate(customer);
Join<Customer,Customer> related = customerSub.join(Customer_.related);
... extra config of subquery
Predicate existsCustomer = cb.exists(sq);
where cq is the CriteriaQuery, and cb is CriteriaBuilder. This comes from an example in the JPA 2.1 spec p323 Example 4

JPA Eclipselink PostgreSQL Query field for Null values

I stumbled over a very strange behaviour with JPA Eclipselink and PostgreSQL.
create table test(id bigint, name varchar(255))
insert into test values(1, "hello")
insert into test values(2, null)
Java EE Entity Bean:
#Entity
#Table(name = "test")
public class Test implements Serializable {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
Stateless Bean with simple JPA Query:
TypedQuery<Test> q = em.createQuery("select t from Test t where t.name = :name", Test.class);
q.setParameter("name", null);
List<Test> list = q.getResultList();
for (Test t : list) {
System.out.println(t.getId() + " " + t.getName());
}
Result:
Aspected: Row with id=2 with null in name field
Current behaviour: No rows returned
When I use "is null" instead of example above in JPA Query. Then I do get something back.
What does JPA do?
select * from test where t.name = null
should this not be the same as
select * from test where t.name is null
JPQL also has the 'is null' comparator that you are required to use to compare to null values. While some JPA providers can interpret and determine that the value passed into an equality is null, and therefore use "IS NULL" in the generated SQL, this is generally not viable when using parameters when the value can change as it means the statement underneath the query must change as well.

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.

Query value of JoinColumn with JPQL

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)