eclipselink JPQL Cascade query probleam - jpa

I have a problem in JPQL cascade query with eclipselink2.5
please see code
Entity code
public class Category{
...
#JoinColumn(name = "parent_category", referencedColumnName = "id")
#ManyToOne(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER,optional = true)
private Category parentCategory;
...
}
JPQL code
String jpql = "select o from Category o order by o.parentCategory.sort ASC";
problem
the problem is this JPQL return list does not include 'o' if 'o.parentCategory' is null.
please see this table http://i.stack.imgur.com/xsXvk.jpg
the return list only rows id is 2,3,4 .
because the column parent_category is null, I lost rows 1,5,6
the correct result should be return all rows
Looking forward to your help!

Using o.parentCategory.sort in the order by clause forces an inner join which filters nulls. If you want nulls included, you will need to use an explicit outer join in the query:
"select o from Category o outer join o.parentCategory parentCategory order by parentCategory.sort ASC"

Related

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));

QueryDSL Left Join not mapping properly with JPA entity

QueryDSL is not working properly with JPA for left join.
I am using queryDSL version 4.2.1 and mapping the response directly to javax.persistence entity.
For Left join/Right join, the joining condition it's not working. It fetches every entity disregarding the joining condition(here it's name = "testName") when entity1.getEntity2() is being called.
Is there any other way applicable for this case to map the result after JOIN tables ?
JPAQuery<Entity1> query = new JPAQuery<>(entityManager);
query.from(table1);
query.leftJoin(table2).on(table2.id.eq(table1.id).and(table2.name.eq("testName"));
List<Entity1> list = query.fetch();
#Entity
public class Entity1{
private Integer id;
#OneToMany(mappedBy = "entity1", fetch = FetchType.LAZY)
private List<Entity2> entity2;
}
An left or right join is an outer join.
So if you use left join all records from the left (in your case table1) will be selected.
If you only want records from table1 if there are corresponding records on table2 you have to use innerJoin.
JPAQuery<Entity1> query = new JPAQuery<>(entityManager);
query.from(table1);
query.innerJoin(table2).on(table2.id.eq(table1.id).and(table2.name.eq("testName"));
List<Entity1> list = query.fetch();
Read more about the join types here:
https://www.diffen.com/difference/Inner_Join_vs_Outer_Join

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

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);