Criteria API is not generating left outer join - java

I have 3 entities, infact many more joined together for brevity i'm skipping those and i'm using open jpa 2.2.2 and oracle 11g. Any thoughts what's going wrong here?
Entity SystemRules{
#OneToMany(mappedBy = "systemRule", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<ServiceActionMap> serviceActionMap;
}
Entity ServiceActionMap{
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "SYSTEM_RULE_ID")
private SystemRules systemRule;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "RFS_TYPE_ID", nullable = true)
private RfsTypeMap rfsType;
}
Entity RfsTypeMap{
#Id
#Column(name="RFS_TYPE_ID" ,nullable=false)
private BigDecimal rfsTypeId;
#Column(name="RFS_NAME")
private String rfsName;
}
Now, I am trying to order the result by RfsTypeMap.rfsName, i'm creating query using criteria builder in following manner
CriteriaQuery<SystemRules> query = cb.createQuery(SystemRules.class);
Root<SystemRules> root= query.from(SystemRules.class);
root.fetch(SystemRules_.serviceActionMap).fetch(ServiceActionMap_.rfsType, JoinType.LEFT);
My order by clause is like this
cb.desc(cb.upper(systemRules.get("serviceActionMap").get("rfsType").get("rfsName").as(String.class)));
Generate JPQL looks like, where i expect a left outer join clause between ServiceActionMap and RfsTypeMap but it's missing. Same gets translated in SQL and i miss those records which are having ServiceActionMap.rfsType as null value.
SELECT DISTINCT s FROM SystemRules s INNER JOIN FETCH s.serviceActionMap INNER JOIN FETCH s.serviceActionMap INNER JOIN FETCH s.ruleProperty INNER JOIN FETCH s.ruleProperty where ... ORDER BY UPPER(s.serviceActionMap.rfsType.rfsName)
I tried going over several answers here but no success, tried explicitly putting a where clause for ServiceActionMap.rfsType is null as suggested on few answers but it's getting ignored, since join happens before where evaluation. Somewhere this question openJPA outer join on optional many-to-one when have order by clause matches my scenario but not able to generate suggested JPQL through criteria API.
I found one related bug on apache jira https://issues.apache.org/jira/browse/OPENJPA-2318. But, not sure that's the case.

I see that every join is repeated twice and even the join alias is always referring to SystemRules. It might be that orderby has caused the repeating inner joins and we may need to explicitly use Join object to refer to extended column.
CriteriaQuery<SystemRules> query = cb.createQuery(SystemRules.class);
Root<SystemRules> root = query.from(SystemRules.class);
Join<SystemRules, ServiceActionMap> join1 = root.join(SystemRules_.serviceActionMap, JoinType.INNER);
Join<ServiceActionMap, RfsTypeMap> join2 = join1.join(ServiceActionMap_.rfsType, JoinType.LEFT);
query.orderBy(cb.desc(cb.upper(join2.get(RfsTypeMap_.rfsName))));

Related

OR condition doesnt work properly in JPA Query

i have the following entities
class ProductUnitOfMessure
#ManyToOne
#JoinColumn(name="PRODUCT_ID")
private Product product;
#ManyToOne
#JoinColumn(name="VARIANT_ID")
private Variant variant;
class Product
#OneToMany(mappedBy = "product", fetch = FetchType.LAZY)
List<Variant> variants;
class Variant
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PRODUCT_ID")
private Product product;
Now when i run the query
select p from ProductUnitOfMeasures p where p.variant.sku=?1 or p.product.sku =?1
(sku of type String).
it return empty list, although there are some as per the below ProductUnitOfMessure table
ProductUnitOfMeasures
when i only run select p from ProductUnitOfMeasures p where p.product.sku =?1
i get some values, why this is happening even though i use OR not AND ?
database is oracle
JPA forces inner joins to be used for references when specifying '.' on a relationship by default. Inner joins automatically exclude nulls from the results - which might be inline with what you might want if you are calling ProductUnitOfMeasures.getVariant().getSku() == youValue as you wouldn't want to deal with NPEs. If you want nulls included in the results, database have what is known as outer join semantics, which allow rows with nulls to be included and still used for the rest of the filter.
Something like:
"select p from ProductUnitOfMeasures p left join p.variant variant left join p.product product where variant.sku = ?1 or product.sku = ?1"
Will allow ProductUnitOfMeasures with either null variant or null product references to still be considered, allowing your ProductUnitOfMeasures with a null variant but with a product.sku value matching your filter to still be returned.

Spring Data Specification Join by specific field

I have a Join looking like:
Join<RepairEntity, RepairAssignEntity> joinRepairAssignEntities = root.join(RepairEntity_.repairAssignEntities, LEFT);
when it converted to SQL it looks like
left outer join
repair_assign repairassi1_
on repairenti0_.id=repairassi1_.repair_id
I want it to be like:
left outer join
repair_assign repairassi1_
on repairenti0_.PARENT_ID=repairassi1_.repair_id
If I add condition to that join:
Predicate equal = cb.equal(root.get(RepairEntity_.parentId), repairEntityPath.get(RepairEntity_.id));
joinRepairAssignEntities = joinRepairAssignEntities.on(equal);
it makes it
left outer join
repair_assign repairassi1_
on repairenti0_.id=repairassi1_.repair_id
and (
repairenti0_.parent_id=repairassi1_.repair_id
)
How do I get rid of
on repairenti0_.id=repairassi1_.repair_id
?
A priori it seems that the entities are not well related, if you want to join by the parent field it should be something like this:
Entity RepairEntity
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="PARENT_ID")
RepairAssignEntity repairAssignEntity;
Entity RepairAssignEntity
#OneToMany(mappedBy="repairAssignEntity", fetch=FetchType.LAZY)
List<RepairEntity> RepairEntities;
if not, you would have to restructure the query to adapt it to the JPA entity modeling.
Regarding to get rid of the condition "on repairenti0_.id = repairassi1_.repair_id" you cannot because it is the implicit one of the relationship defined in the entity, but if you modify the modeling as I indicate, only the condition "repairenti0_.parent_id = repairassi1_ will appear. .repair_id "

JPA2 Criteria: How to avoid a cross join using path.get()

If you have this entity:
#Entity
public class A {
#ManyToOne
#JoinColumn(name = "bField", nullable = true)
private B myBObject;
}
And I have a generic generator of Criteria who will do that:
Root<A> root = criteria.from(A.class);
root.get("myBObject").get("aFieldInB");
The problem is the following: the generated sql will contains a CROSS JOIN between A and B.
But I would like that the generated sql will contains a LEFT JOIN between A and B.
How can I do that?
You must use a join(). In general it is better to always use a join() for relationships.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Criteria#JoinType

Using Restrictions.disjunction over #JoinTable association

This is similar, but not identical, to:
Hibernate criteria query on different properties of different objects
I have a SpecChange record, which has a set of ResponsibleIndividuals; these are User records mapped by a hibernate join-table association. I want to create a Criteria query for a SpecChange which has the specified User in its ResponsibleIndividuals set, OR some other condition on the SpecChange.
I'm omitting most of the code for clarity, just showing the relevant annotations:
#Entity
class SpecChange {
#OneToMany
#JoinTable(name = "ri_id_spec_change_id", joinColumns = { #JoinColumn(name = "spec_change_id") }, inverseJoinColumns = #JoinColumn(name = "ri_id"))
#AccessType("field")
public SortedSet<User> getResponsibleIndividuals() { ... }
#Id
#Column(name = "unique_id")
#AccessType("field")
public String getId() { ... }
}
#Entity
class User { ... }
//user does not have a SpecChange association (the association is one-way)
What I want to do:
User currentUser = ...;
Criteria criteria = session.createCriteria(SpecChange.class);
...
criteria.add(Restrictions.disjunction()
.add(Restrictions.eq("responsibleIndividuals", currentUser))
.add(...)
);
criteria.list();
This generates wrong SQL:
select ... from MY_DB.dbo.spec_change this_ ... where ... (this_.unique_id=?)
...and fails:
java.sql.SQLException: Parameter #2 has not been set.
(I omitted another condition in the where clause, hence parameter #2 is the one shown. I am sure that 'currentUser' is not null.)
Note that the restriction references the wrong table: this_, which is SpecChange, not the User table
I tried a dozen different tricks to make it work correctly (including creating an alias, as mentioned in the previous post above). If there is a way to do it using the alias, I wasn't able to determine it.
The following DOES work (but doesn't accomplish what I need, since I can't use it in a disjunction):
criteria.createCriteria("responsibleIndividuals").add(Restrictions.idEq(currentUser.getId()));
[Edit: a workaround for what seems like a bug in Hibernate, using HQL]
select sc
from com.mycompany.SpecChange sc
left join fetch sc.responsibleIndividuals as scResponsibleIndividuals
where scResponsibleIndividuals = :p1 or sc.updUser = :p1
order by sc.updDate desc
This won't work without the alias "scResponsibleIndividuals"

On-demand eager loading

I make a query:
String query = "SELECT DISTINCT a FROM A a FETCH ALL PROPERTIES " +
"JOIN a.Bs AS b " +
"JOIN b.Cs AS c WHERE c = :c";
Query q = DAO.getSession().createQuery(query);
q.setParameter("c", c);
return q.list();
Even though I've said FETCH ALL PROPERTIES on a, when I access all the collections that A has, they still need to be loaded, thus aren't eagerly loaded. They have been defined as lazy loading, and that is the default behaviour I want, but this is the exception: I would like them loaded right now. I've tried swapping JOIN for LEFT OUTER JOIN to provoke Hibernate into loading them, and I've tried setting q.setFetchMode("a", FetchMode.EAGER), but it doesn't exist for Query.
The list of As is quite long, and they have quite a few collections, so making this an n+1 query thing is very slow (about ten seconds, as opposed to doing it in a single query which would be sub-second speed). I'd far prefer one query and loading all that's necessary in that one go. Any suggestions on how I can do that?
PS, Little bonus question: If I replace the "JOIN b.Cs AS c WHERE c = :c";
line with "WHERE :c IN b.Cs";, I get an SQL exception:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '))' at line 1
The double paranthesis it's referring to is "and ('151000000-0000' in (.))" where 151000000-0000 is the primary key of c. Any idea why I get this error when I do it this way compared to not getting it when I do it the with joining b.Cs in?
UPDATE, as requested, here is the way I use for mapping. B and C are very similarly designed:
#Entity
#Table(name = "tblA")
public class A {
#Id
String AId;
#Column(name = "shortName", length = 12, nullable = false)
String shortName;
#OneToMany(fetch=FetchType.LAZY, mappedBy="theA")
private Set<B> Bs;
#OneToMany(fetch=FetchType.LAZY, mappedBy="theA")
private Set<D> Ds;
#OneToMany(fetch=FetchType.LAZY, mappedBy="theA")
private Set<E> Es;
#OneToMany(fetch=FetchType.LAZY, mappedBy="theA")
private Set<F> Fs;
}
theA in B, D, E and F is defined like this:
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "AId", nullable = true)
#ForeignKey(name="FK_KategoriID")
private A theA;
Cheers
Nik
fetch all properties is not what you want; it's used for telling Hibernate that you want it to fetch single-valued lazy-loaded properties. Details are here.
You need to specify join fetch in your query instead:
SELECT DISTINCT a FROM A a
LEFT JOIN FETCH a.Bs AS b
LEFT JOIN FETCH b.Cs AS c
WHERE c = :c
As far as bonus question goes, WHERE :c IN b.Cs is illegal syntax. Depending on how your C is mapped, you may want to look at elements() function instead.
FETCH ALL PROPERTIES only works for lazy properties (where a property is a String, Integer, ...) not one-to-may associations (i.e. collections)
see A Short Primer On Fetching Strategies

Categories