Multiple JOIN with FETCH in Criteria API - jpa

I have a JPQL that looks like this:
SELECT
DISTINCT b
FROM
Book b
INNER JOIN
FETCH b.volumes as v
INNER JOIN
v.metadata as md
WHERE
b.createdAt IN (
SELECT
MAX(book.createdAt)
FROM
Book book
WHERE
book.author = ?1
)
AND md.genre IN (
?2
)
Since this has to be generated dynamically, I need to use Criteria API to add various conditions. I can get everything right except for the JOINs, because the first one is a fetch and I'm not able to chain that one with the next.
This is what I have so far:
query.distinct(true);
final Join<Book, Metadata> Metadata = root.join("volumes").join("metadata");
final Subquery<LocalDateTime> subquery = query.subquery(LocalDateTime.class);
final Root<Book> plan = subquery.from(Book.class);
subquery.where(criteriaBuilder.equal(plan.get("author"), companyId));
final Expression<LocalDateTime> createdAt = book.get("createdAt");
subquery.select(criteriaBuilder.greatest(createdAt));
predicates.add(criteriaBuilder.in(root.get("createdAt")).value(subquery));
genre.ifPresent(strings -> predicates.add(criteriaBuilder.in(metadata.get("genre")).value(strings)));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
Any idea on how to make the first join a fetch?

There is a method for it in the Root class. Without second parameter it is inner join fetch
root.fetch("volumes")

Related

How do I return only the first object in an HQL cross join?

I have the following HQL
from
com.kable.web.allotment.model.Issue i
inner join fetch i.title
inner join fetch i.title.magazine
inner join fetch i.barcodes bcs
, Wholesaler w
LEFT join fetch w.localCurrencies c
inner join fetch w.location
where
w.id = :wholesalerId
and i.title.id = :titleid
and i.distributionStatus = :status
and (
(
i.distributionDate is null
and i.onSaleDate >= TRUNC(CURRENT_DATE)
)
or i.distributionDate >= TRUNC(CURRENT_DATE)
)
and bcs.type.id = w.location.id
and (bcs.localCurrency.id = c.localCurrencyType.id OR c.localCurrencyType.id IS NULL)
and i.onSaleDate BETWEEN COALESCE(c.effectiveDate, i.onSaleDate) and COALESCE(c.expirationDate, i.onSaleDate)
order by
i.distributionDate
, i.onSaleDate
All of my previously written code is expecting to get a List<Issue> back, but with the code above, I am also getting the wholesaler and its joins. In my results, I only want Issue, Title, Magazine, and Barcodes. I am using hibernate version 4.2.18.Final. How do I only return the 1st object graph? I found something about CROSS JOIN ON but it is only for Hibernate 5 or later, and I can't switch because the project is quite large and Java dependencies.
You simply need to add an explicit SELECT i clause.
As a side note, JOIN FETCH for Wholesaler associations doesn't make sense if it's not going to be present in the result anyway

T-SQL: Joining on two separate fields based on specific criteria in a query

I have a query in which I am trying to get additional fields from another table through a join field that I manually create. The issue is when the field I create is null, then I want to use another field to join on. I am not sure how to do that without getting duplicate results. I tried a UNION query, but that just displays everything where the values are null when the manually created field value is null. Here is the query:
SELECT
BU = m.BU,
BUFBA = m.BUFBA,
a.CostCenter,
Delegate = m.Delegate,
a.DistrictLookup,
PCOwner = m.PCOwner,
a.PGr,
a.POrg,
PrimaryContact = m.PrimaryContact,
WarehouseManager = m.WarehouseManager,
Zone = m.Zone,
ZoneFBA = m.ZoneFBA
FROM
(SELECT
e.CostCenter,
e.District,
DistrictLookup =
CASE
WHEN e.PGr IN ('N01','BQE','BQA') THEN 'GSS'
WHEN e.PGr = 'BQB' THEN 'BG'
WHEN e.PGr = 'BQF' THEN 'FP'
ELSE e.District
END,
e.PGr,
e.POrg
FROM dbo.E1P e (NOLOCK)
WHERE
e.CoCd = '4433'
) a
LEFT JOIN dbo.Mapping m (NOLOCK) ON m.District = a.DistrictLookup
When the DistrictLookup field is NULL, I need a different join to occur so that the additional fields populate. That join would be:
LEFT JOIN dbo.Mapping m (NOLOCK) ON m.CostCenter = a.CostCenter
How can I write in this second join and not get duplicate results? This is a separate join on different fields and I think it differs from the other methods of doing a conditional join. If it, can someone please explain how to implement that logic into my query?
I believe this is what you are after...
LEFT JOIN dbo.Mapping m (NOLOCK)
ON (a.DistrictLookup IS NOT NULL AND m.District = a.DistrictLookup)
OR (a.DistrictLookup IS NULL AND m.CostCenter = a.CostCenter)

JPA Query over a join table

I have 3 tables like:
A AB B
------------- ------------ ---------------
a1 a1,b1 b1
AB is a transition table between A and B
With this, my classes have no composition within these two classes to each other. But I want to know that , with a JPQL Query, if any records exist for my element from A table in AB table. Just number or a boolean value is what I need.
Because AB is a transition table, there is no model object for it and I want to know if I can do this with a #Query in my Repository object.
the AB table must be modeled in an entity to be queried in JPQL. So you must model this as
an own entity class or an association in your A and or your B entity.
I suggest to use Native query method intead of JPQL (JPA supports Native query too). Let us assume table A is Customer and table B is a Product and AB is a Sale. Here is the query for getting list of products which are ordered by a customer.
entityManager.createNativeQuery("SELECT PRODUCT_ID FROM
SALE WHERE CUSTOMER_ID = 'C_123'");
Actually, the answer to this situation is simpler than you might think. It's a simple matter of using the right tool for the right job. JPA was not designed for implementing complicated SQL queries, that's what SQL is for! So you need a way to get JPA to access a production-level SQL query;
em.createNativeQuery
So in your case what you want to do is access the AB table looking only for the id field. Once you have retrieved your query, take your id field and look up the Java object using the id field. It's a second search true, but trivial by SQL standards.
Let's assume you are looking for an A object based on the number of times a B object references it. Say you are wanting a semi-complicated (but typical) SQL query to group type A objects based on the number of B objects and in descending order. This would be a typical popularity query that you might want to implement as per project requirements.
Your native SQL query would be as such:
select a_id as id from AB group by a_id order by count(*) desc;
Now what you want to do is tell JPA to expect the id list to comeback in a form that that JPA can accept. You need to put together an extra JPA entity. One that will never be used in the normal fashion of JPA. But JPA needs a way to get the queried objects back to you. You would put together an entity for this search query as such;
#Entity
public class IdSearch {
#Id
#Column
Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Now you implement a little bit of code to bring the two technologies together;
#SuppressWarnings("unchecked")
public List<IdSearch> findMostPopularA() {
return em.createNativeQuery("select a_id as id from AB group by a_id
order by count(*) desc", IdSearch.class).getResultList();
}
There, that's all you have to do to get JPA to get your query completed successfully. To get at your A objects you would simply cross reference into your the A list using the traditional JPA approach, as such;
List<IdSearch> list = producer.getMostPopularA();
Iterator<IdSearch> it = list.iterator();
while ( it.hasNext() ) {
IdSearch a = it.next();
A object = em.find(A.class,a.getId());
// your in business!
Still, a little more refinement of the above can simplify things a bit further actually given the many many capabilities of the SQL design structure. A slightly more complicated SQL query will an even more direct JPA interface to your actual data;
#SuppressWarnings("unchecked")
public List<A> findMostPopularA() {
return em.createNativeQuery("select * from A, AB
where A.id = AB.a_id
group by a_id
order by count(*) desc", A.class).getResultList();
}
This removes the need for an interm IdSearch table!
List<A> list = producer.getMostPopularA();
Iterator<A> it = list.iterator();
while ( it.hasNext() ) {
A a = it.next();
// your in business!
What may not be clear tot the naked eye is the wonderfully simplified way JPA allows you to make use of complicated SQL structures inside the JPA interface. Imagine if you an SQL as follows;
SELECT array_agg(players), player_teams
FROM (
SELECT DISTINCT t1.t1player AS players, t1.player_teams
FROM (
SELECT
p.playerid AS t1id,
concat(p.playerid,':', p.playername, ' ') AS t1player,
array_agg(pl.teamid ORDER BY pl.teamid) AS player_teams
FROM player p
LEFT JOIN plays pl ON p.playerid = pl.playerid
GROUP BY p.playerid, p.playername
) t1
INNER JOIN (
SELECT
p.playerid AS t2id,
array_agg(pl.teamid ORDER BY pl.teamid) AS player_teams
FROM player p
LEFT JOIN plays pl ON p.playerid = pl.playerid
GROUP BY p.playerid, p.playername
) t2 ON t1.player_teams=t2.player_teams AND t1.t1id <> t2.t2id
) innerQuery
GROUP BY player_teams
The point is that with createNativeQuery interface, you can still retrieve precisely the data you are looking for and straight into the desired object for easy access by Java.
#SuppressWarnings("unchecked")
public List<A> findMostPopularA() {
return em.createNativeQuery("SELECT array_agg(players), player_teams
FROM (
SELECT DISTINCT t1.t1player AS players, t1.player_teams
FROM (
SELECT
p.playerid AS t1id,
concat(p.playerid,':', p.playername, ' ') AS t1player,
array_agg(pl.teamid ORDER BY pl.teamid) AS player_teams
FROM player p
LEFT JOIN plays pl ON p.playerid = pl.playerid
GROUP BY p.playerid, p.playername
) t1
INNER JOIN (
SELECT
p.playerid AS t2id,
array_agg(pl.teamid ORDER BY pl.teamid) AS player_teams
FROM player p
LEFT JOIN plays pl ON p.playerid = pl.playerid
GROUP BY p.playerid, p.playername
) t2 ON t1.player_teams=t2.player_teams AND t1.t1id <> t2.t2id
) innerQuery
GROUP BY player_teams
", A.class).getResultList();
}

In JPA 2.0 JPQL, when one returns a NEW object, how may one make use of FETCH JOINs?

A colleague of mine has the following (apparently invalid) JPQL query:
SELECT NEW com.foobar.jpa.DonationAllocationDTOEntity(a.id, a.campaign, a.campAppeal, a.campDivision, a.divisionFund)
FROM DonationAllocation a JOIN a.donation d JOIN a.allocationType t
JOIN FETCH a.campaign
WHERE d.id = :donationId
AND (t.code = 'Pledge' OR t.code = 'MatchingPledge')
It is worth noting (for later in this message) that DonationAllocation's relationship with a Campaign entity is many-to-one, and is marked as FetchType.LAZY. My colleague's intent with this query is to (among other things) ensure that a.campaign is "inflated" (eagerly fetched).
Hibernate (obviously just one JPA implementation of several), when faced with this query, says:
query specified join fetching, but the owner of the fetched association was not present in the select list
This makes sense, as the select list contains only NEW DonationAllocationDTOEntity(), and section 4.4.5.3 of the JPA 2.0 specification says:
The association referenced by the right side of the FETCH JOIN clause must be an association or element collection that is referenced from an entity or embeddable that is returned as a result of the query.
So since there is no "entity or embeddable that is returned as a result of the query" (it's a DTO constructed using the NEW operator), it follows that there is no possible association for a FETCH JOIN to reference, and hence this query is invalid.
How, given this limitation, should one construct a JPQL query in this case such that a.campaign--passed into the constructor expression--is fetched eagerly?
I would simply select the entity and its association, and llopover the results to invoke the DTO constructor explicitely. You would have the additional advantage of compile-time checks and refactorable code:
select a from DonationAllocation a
JOIN a.donation d
JOIN a.allocationType t
JOIN FETCH a.campaign
WHERE d.id = :donationId
AND (t.code = 'Pledge' OR t.code = 'MatchingPledge')
...
for (DonationAllocation a : list) {
result.add(new DonationAllocationDTOEntity(a.id,
a.campaign,
a.campAppeal,
a.campDivision,
a.divisionFund));
}
EDIT:
This query should also select what's needed, and avoid selecting the whole DonationAllocation entity:
select a.id, a.campaign, a.campAppeal, a.campDivision, a.divisionFund
from DonationAllocation a
JOIN a.donation d
JOIN a.allocationType t
WHERE d.id = :donationId
AND (t.code = 'Pledge' OR t.code = 'MatchingPledge')
and you might just add the DTO constructor in the query if you want:
select new com.foobar.jpa.DonationAllocationDTOEntity(a.id, a.campaign, a.campAppeal, a.campDivision, a.divisionFund)
from DonationAllocation a
JOIN a.donation d
JOIN a.allocationType t
WHERE d.id = :donationId
AND (t.code = 'Pledge' OR t.code = 'MatchingPledge')
The fact the a.campaign is in the select clause should be sufficient to tell Hibernate to load the entity. At least that's how it behaves in my tests.

Joining several tables: table specified more than once error

I am attempting to call data after joining all of my tables in a postgreSQL query.
I am getting the following error:
Error in postgresqlExecStatement(conn, statement, ...) :
RS-DBI driver: (could not Retrieve the result : ERROR: table name "place" specified more than once
)
Failed to execute SQL chunk
After referring to similar posts and online resources, I have attempted setting aliases (which only create new errors about not referring to the other tables in the FROM clause), reordering the table names (the cleanest, reordered chunk is posted below), and experimenting with UPDATE/FROM/JOIN as an alternative to SELECT/FROM/JOIN. However, I think I ultimately need to be using the SELECT clause.
SELECT *
FROM place
INNER JOIN place ON place.key = form.key
INNER JOIN form ON form.id = items.formid
INNER JOIN items ON items.recid = rec.id
INNER JOIN rec ON rec.id = sub.recid
INNER JOIN sub ON sub.id = type.subid
INNER JOIN type ON type.name = det.typeid;
You have "place" in your query twice, which is allowed but you would have to alias the second use of the table "place". Also you reference something called "det", but it's not a table or alias anywhere in your query. If you want only one place that's fine, just remove the INNER JOIN place and move your place.key = form.key to the form join or to a where clause.
If you want to place tables in their because you are trying to join the table to itself the alias the second one, but you will want to make sure that you then have a clause to join those two tables on (could be part of an on or a where)
The issue ended up being the order of table names. The code below joined everything just fine:
SELECT *
FROM place
INNER JOIN form ON place.key = form.key
INNER JOIN items ON form.id = items.formid
INNER JOIN rec ON items.recid = rec.id
INNER JOIN sub ON rec.id = sub.recid
INNER JOIN type ON sub.id = type.subid
INNER JOIN det ON type.name = det.typeid;