jpa avoid query - jpa

i have the next class
#Entity
#Table(name = "table_order")
#IdClass(OrderPK.class)
public class Order {
/** Unique identifier of the currency code in which the transaction was negociated. */
#Column(name = "TRADECURRE", nullable = false, length = 5)
private String tradeCurrencyCode;
/** Currency Entity for Trade. */
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "TRADECURRE", referencedColumnName = "codigo", updatable = false, insertable = false) })
private Currency currencyEntity;
.. here get and sets
}
then execute the next query:
StringBuilder jpaQuery = new StringBuilder();
StringBuilder whereClause = new StringBuilder();
jpaQuery.append("SELECT o, o.currencyEntity");
List orders = query.getResultList();
in this point the log of jpa show 2 querys executed, one to order table and other to Currency table.
bellow i write the next code (in the same class and method of the previous code)
for (Object orderElement : orders) {
int indexArray = 0;
Object[] orderArray = (Object[]) orderElement;
Order orderEntity = (Order) orderArray [indexArray++];
orderEntity.setCurrencyEntity((Currency) orderArray [indexArray++]);
}
When the line
orderEntity.setCurrencyEntity((Currency) orderArray [indexArray++]);
is executed, the query over the table currency is executed once again at database. I need avoid this query to fix some performance problems, i have all the data in the orderArray.
i'm using eclipselink 1.1
thanks in advance

This is happening because you haven't told JPA to pre-fetch the currentEntity in the initial select (although I think that's what you were trying to do with SELECT o, o.currencyEntity). As a result, JPA has to fetch the currentEntity each time round the loop, and it's a real performance killer.
The way to do this with JPA is with a fetch join (documented here). You'd write your query like this:
SELECT o from Order o LEFT JOIN FETCH o.currencyEntity
This also makes it easier to navigate the result set than with SELECT o, o.currencyEntity, since you'll only have a single entity returned, with its currencyEntity property intact:
List<Order> orders = query.getResultList();
for (Order order : orders) {
// fully-populated, without requiring another database query
Currency ccy = order.getCurrentEntity();
}

Related

JPA Criteria subquery with tuple

I define two Entity
#Entity
class Template {
#Id
private String id;
private String name;
#OneToMany(fetch=FetchType.LAZY, mappedBy="template")
private List<Edition> editions;
}
class Edition {
#Id
private String id;
private Integer version;
private String state;
#ManyToOne(fetch=FetchType.LAZY)
private Template template;
}
And I want to query each template's newest version and edition's state, so my native sql is:
select
a.id, a.name, b.version, b.state
from
tb_pm_template a,
tb_pm_edition b
where
a.id = b.template_id and
(b.template_id, b.version) in (select template_id, max(version) from tb_pm_edition group by template_id)
The navtive sql work fine. But I want to write in jpa Criteria api way.
So, I try the code below:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> maxVersionQuery = cb.createTupleQuery();
Root<Edition> editionRoot = maxVersionQuery.from(Edition.class);
maxVersionQuery.multiselect(
editionRoot.get("template").get("id").alias("templateId"),
cb.max(editionRoot.get("version")).alias("maxVersion")
).groupBy(editionRoot.get("template").get("id"));
List<Tuple> maxVersion = entityManager.createQuery(maxVersionQuery).getResultList();
List<Map<String, Object>> maxVersionResult = new ArrayList<>(maxVersion.size());
for (Tuple tuple: maxVersion) {
Map<String, Object> row = new HashMap<>(2);
for (TupleElement element: tuple.getElements()) {
row.put(element.getAlias(), tuple.get(element.getAlias()));
}
maxVersionResult.add(row);
}
// the maxVersion or maxVersionResult contain the template's newest version info I want, then I want to combine the version state and template name
CriteriaQuery<Tuple> templateQuery = cb.createTupleQuery();
Root<Edition> editionRoot1 = templateQuery.from(Edition.class);
templateQuery.multiselect(
editionRoot1.get("template").get("id").alias("id"),
editionRoot1.get("template").get("name").alias("name"),
editionRoot1.get("version").alias("version"),
editionRoot1.get("state").alias("versioinState")
).where(
// here I don't know how to connect the conditions
// I try the cb.in, but it needs Expression type
// I also try to use the Subquery api, but since I need the subquery return template_id and version, so I define Subquery<Tuple>, but the Subquery instance's select method only take one parameter
// I check the official document in comments, the example only show the aggregate without groupby
);
Is my implementation way wrong? On this basis, I also need to add paging and sorting, so if it is divided into two or more SQL statements, will it affect the paging count?

How to join fetch an attribute of element in collection with QueryDSL JPA

Entities as below:
class A {
Long id;
#ManyToMany
#JoinTable(name = "rel_a_b", joinColumns = #JoinColumn(name = "a_id"), inverseJoinColumns = #JoinColumn(name = "b_id"))
Set<B> bSet;
}
class B {
Long id;
#ManyToMany(mappedBy = "bSet")
Set<A> aSet;
#ManyToMany
#JoinTable(name = "rel_b_c", joinColumns = #JoinColumn(name = "b_id"), inverseJoinColumns = #JoinColumn(name = "c_id"))
Set<C> cSet;
}
class C {
Long id;
#ManyToMany(mappedBy = "cSet")
Set<B> bSet;
}
I need to select A entities and join fetch bSet and cSet in B entity. Using JPA Criteria, codes as below:
final Fetch<A, B> bSetFetch = rootA.fetch("bSet", JoinType.LEFT);
bSetFetch.fetch("cSet", JoinType.LEFT);
are working perfectly, but I can't achieve this with QueryDSL. I tried
final QA a = QA.a;
jpaQuery
.from(a)
.leftJoin(a.bSet, QB.b).fetchJoin()
.leftJoin(QB.b.cSet).fetchJoin()
.select(a)
but it throws exception that
query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=b,role=A.b,tableName=`b`,tableAlias=b4_,origin=a a2_,columns={a2_.id ,className=B}}] [select a
from A a
left join fetch a.bSet as b
left join fetch b.cSet]]
, if without fetchJoin(), the results don't include bSet and cSet. Could anyone solve this?
The fetch joins are applied correctly from a QueryDSL point of view. We can also observe this from the fact that the produced JPQL query looks correct.
The limitation here is that Hibernate only allows FETCH JOINS if the owner of the fetch association is projected in the select clause. cSet is an association on B, so you would need to project your b's or omit the fetch join for cSet. For example:
jpaQuery
.from(a)
.leftJoin(a.bSet, QB.b).fetchJoin()
.leftJoin(QB.b.cSet).fetchJoin()
.select(a, b)
Now this will result in duplicate results for a due to the cardinality of bSet. That is just a limitation of fetch joins in Hibernate.
Alternatively, you could consider specifying a fetch graph for the query:
EntityGraph postGraph = em.getEntityGraph("post");
query.setHint("javax.persistence.fetchgraph", postGraph);
For more information on using EntityGraphs see https://www.baeldung.com/jpa-entity-graph

EclipseLink ManyToOne - CriteriaBuilder Generated Query is Wrong

I have an Entity with a ManyToOne Relationship to the Primary Key of another entity. When I create a query that references this Foreign Key eclipseLink always creates a join instead of simply accessing the Foreign Key.
I have created a highly simplified example to show my issue:
#Entity
public class House {
#Id
#Column(name = "H_ID")
private long id;
#Column(name = "NAME")
private String name;
#ManyToOne
#JoinColumn(name = "G_ID")
private Garage garage;
}
#Entity
public class Garage{
#Id
#Column(name = "G_ID")
private long id;
#Column(name = "SPACE")
private Integer space;
}
I created a query that should return all houses that either have no garage or have a garage with G_ID = 0 using the CriteriaBuilder.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<House> query = cb.createQuery(House.class);
Root<House> houseRoot = query.from(House.class);
Path<Long> garageId = houseRoot.get(House_.garage).get(Garage_.id);
query.where(cb.or(cb.equal(garageId , 0), cb.isNull(garageId)));
TypedQuery<House> typedQuery = entityManager.createQuery(query);
List<House> houses = typedQuery.getResultList();
The generated query is:
SELECT h.NAME, h.G_ID FROM HOUSE h, GARAGE g WHERE (((h.G_ID= 0) OR (g.G_ID IS NULL)) AND (g.G_ID = h.G_ID));
I don't understand why
The or condition first references table HOUSE and then GARAGE (instead of HOUSE)
The join is created in the first place.
The correct query should look like this in my understanding:
SELECT h.NAME, h.G_ID FROM HOUSE h WHERE (((h.G_ID= 0) OR (h.G_ID IS NULL));
Or if a join is made it should take into account that the ManyToOne relationship is nullable and therefore do a LEFT OUTER JOIN.
SELECT h.NAME, h.G_ID FROM HOUSE h LEFT OUTER JOIN GARAGE g ON (h.G_ID = g.G_ID ) WHERE (h.G_ID = 0) OR (g.G_ID IS NULL);
(Note both these queries would work correctly in my more complicated setup. I also get the same error when only wanting to retrieve all houses that have no garage.)
How can I achieve this (while still using the CriteriaBuilder and ideally not having to change the DB Model)?
(Please let me know any additional information that might be required, I'm very new to this topic and came across this issue while migrating an existing application.)
-- edit --
I have found a solution to my problem that will result in slightly different behaviour (but in my application that part of the code I had to migrate didn't make much sense in the first place). Instead of using
Path<Long> garageId = houseRoot.get(House_.garage).get(Garage_.id);
I use
Path<Garage> garage = houseRoot.get(House_.garage);
And then as expected table Garage isn't joined anymore. (I assume the code previously must have been some kind of hack to get the desired behaviour from openJPA)
I don't understand why
The or condition first references table HOUSE and then GARAGE (instead of HOUSE)
I believe this is implementation specific; in any case, it shouldn't have any bearing on the results.
The join is created in the first place.
By saying Path<Long> garageId = houseRoot.get(House_.garage).get(Garage_.id) you're basically telling EclipseLink: 'join Garage to House, we're gonna need it'. That you then access Garage_.id (and not, for example, Garage_.space) is inconsequential.
If you don't want the join, simply map the G_ID column one more time as a simple property: #Column(name = "G_ID", insertable = false, updatable = false) private Long garageId. Then refer to House_.garageId in your query.
Or if a join is made it should take into account that the ManyToOne relationship is nullable and therefore do a LEFT OUTER JOIN.
Path.get(...) always defaults to an INNER JOIN. If you want a different join type, use Root.join(..., JoinType.LEFT), i. e. houseRoot.join(House_.garage, JoinType.LEFT).get(Garage_.id).
One solution that results in the same behaviour is:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<House> query = cb.createQuery(House.class);
Root<House> houseRoot = query.from(House.class);
Path<Garage> garage = houseRoot.get(House_.garage);
Path<Long> garageId = garage.get(Garage_.id);
query.where(cb.or(cb.equal(garageId , 0), cb.isNull(garage)));
TypedQuery<House> typedQuery = entityManager.createQuery(query);
List<House> houses = typedQuery.getResultList();
This results in the following SQL:
SELECT H_ID, NAME, G_ID FROM HOUSE WHERE ((G_ID = 0) OR (G_ID IS NULL));

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.

QueryDsl - OR statement not working

I have the following QueryDSL query:
QCustomer customer = QCustomer.customer;
BooleanBuilder builder = new BooleanBuilder();
builder.or(customer.person.name.containsIgnoreCase(query));
builder.or(customer.company.name.containsIgnoreCase(query));
return builder;
And I expect to get results from Persons that contains the name = query and/or Companies that contains the query parameter. But I get nothing.
This is my Customer class mapping:
#OneToOne(orphanRemoval = false, optional = true, cascade = CascadeType.ALL)
private Company company;
#OneToOne(orphanRemoval = false, optional = true, cascade = CascadeType.ALL)
private Person person;
Did someone knows what I'm missing here?
I expect to get a query like this:
select o
from Customer
where o.person.name like '%:name%' or o.company.name like '%:name%'
This is the generated query:
select
count(customer0_.uid) as col_0_0_
from
Customer customer0_
cross join
Person person1_
cross join
Company company2_
where
customer0_.person_uid=person1_.uid
and customer0_.company_uid = company2_.uid
and (lower(person1_.name) like ? escape '!' or lower(company2_.name) like ? escape '!') limit ?
It uses a count because it's the first query that Spring Data use to paginate the result.
The query looks ok. Most probably you get wrong results because the implicit property based joins make the joins inner joins.
Using left joins you might get the results you need.
QPerson person = QPerson.person;
QCompany company = QCompany.company;
BooleanBuilder builder = new BooleanBuilder();
builder.or(person.name.containsIgnoreCase(str));
builder.or(company.name.containsIgnoreCase(str));
query.from(customer)
.leftJoin(customer.person, person)
.leftJoin(customer.company, company)
.where(builder);