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

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

Related

Criteria API is not generating left outer join

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

JPA Query Missing alias and column (Hibernate)

I have the following relevant JPA annotated classes in a Spring-Boot JPA enabled project (All Groovy Code):
#Entity
abstract class Character {
#Id
String id;
String name;
#ElementCollection(targetClass = Episode)
#Enumerated(EnumType.ORDINAL)
Collection<Episode> appearsIn;
}
#Entity(name = "Human")
public class Human extends Character {
String homePlanet;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "favorite_droid_id")
Droid favoriteDroid;
}
public enum Episode {
PHANTOM_MENACE,
ATTACK_OF_THE_CLONES,
REVENGE_OF_THE_SITH,
A_NEW_HOPE,
EMPIRE_STRIKES_BACK,
RETURN_OF_THE_JEDI,
THE_FORCE_AWAKENS
}
When I attempt to execute the following JPA Query:
def query = em.createQuery("from Human h where h.appearsIn in (:episodes)");
query.setParameter("episodes", EnumSet.of(Episode.THE_FORCE_AWAKENS));
def result = query.getResultList();
The generated SQL statement does not seem to have the alias to the Character table or the column name for appears_in:
select human0_.id as id2_0_, human0_.name as name3_0_, human0_.favorite_droid_id as favorite6_0_, human0_.home_planet as home_pla5_0_
from character human0_
cross join character_appears_in appearsin1_
where human0_.dtype='Human' and human0_.id=appearsin1_.character_id and (. in (?))
I have also tried using equals instead of in, with the same behavior:
from Human h where h.appearsIn = :episode
Produces the following SQL:
select human0_.id as id2_0_, human0_.name as name3_0_, human0_.favorite_droid_id as favorite6_0_, human0_.home_planet as home_pla5_0_
from character human0_
cross join character_appears_in appearsin1_
where human0_.dtype='Human' and human0_.id=appearsin1_.character_id and .=?
Any help is greatly appreciated.
Your query is invalid - as #Neil Stockton pointed out, by writing h.appearsIn in (:episodes) you are saying "collection in collection" which does not make sense.
You should rather declare a "collection member variable" like this:
select distinct h
from Human h
join h.appearsIn ai
where ai in (:episodes)
ai represents a single element of appearsIn (like an iterator).

Select subset of the list contained in an entity

Say I want to get all rows of the MyEntity that have an id lower than 10. This entity contains a list of Another entity. I would like this list to be fetched only by a subset of the listAnother. This subset containing only Another where the user contained in it is a specific one.
Basically in SQL it would be like this :
SELECT * FROM myentity m
LEFT JOIN another a
ON m.idTable=a.my_entity
AND a.user = "test1"
WHERE m.idTable < 10;
I didn't manage however to translate this query to jpql.
My entities being like this :
#Entity
public class MyEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int idTable;
#OneToMany(mappedBy = "myEntity")
private List<Another> listAnother;
}
#Entity
public class Another implements Serializable {
#Id
private int idAnother;
// bi-directional many-to-one association to Thethread
#ManyToOne(fetch = FetchType.LAZY)
private MyEntity myEntity;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
}
#Entity
public class User implements Serializable {
#Id
private String username;
}
In jpa I could do this :
SELECT m FROM MyEntity where m.idTable < 10;
And then for each entity I get from this list call this:
SELECT a FROM Another Where a.user.username=:'test' AND a.myEntity=:entity;
However I would like to do it all at once in one query. Can I do this with criteria ? I didn't take the time to learn it but if it's possible then I will.
JPQL and Critaria API are equal in terms of what you can express with them. What is possible with JPQL is possible with Criteria and vice versa.
With JPQL, you can simply combine your 2 queries into one in this way:
SELECT a FROM Another a Where a.user.username=:test AND a.myEntity.idTable < 10
You can use dot notation (.) to join multiple entities in the query, provided that the relationship is X-to-one. If you have X-to-many relationship, you need to use JPQL JOIN, which is not very complicated. Example with (LEFT) JOIN:
SELECT m FROM MyEntity m LEFT JOIN m.listAnother a Where a.user.username=:test AND m.idTable < 10
The result is of course not equal - in first case you will get list of Another entities and you can get MyEntity by a.myEntity, in the second case you will get list of MyEntity, which all have at least one Another entity with given user
In hibernate you can use Filters and FilterJoinTable. Here you can read how to do that. Similar problem was solved here.
You need to extend the logic which you applied to check the username (a.user.username=:'test') which was for many-to-one relation between anything and user by taking it one level up to myEntity and then using it for one-to-many relation as well -
SELECT m FROM MyEntity where m.idTable < 10 and (m.listAnother.user.username=:'test')
The join condition "m.listAnother.myEntity=:entity" wouldn't be needed now as in our query we have started from the specific myEntity and then moved down to listAnother.user.username .
I don't have the table definitions to try this query myself, exact SQL may require some tweaks - but logically it should work like the way I showed above, i.e. just the way you joined Another with User, the same way you can join MyEntity with Another by just traversing down the child listAnother.

Hibernate Query.list returns actual Object instance instead of expected type

When performing a hibernate query in HQL using a join and subsequently calling query.list to return the list of matched objects, I am ending up with a list of actual Object instances (i.e. query.list().get(0).getClass() == Object.getClass()) instead of instances of the expected object.
Running the query without the join returns objects of the expected type correctly and they can be cast and used appropriately.
So far my searches have not turned up anything about what could be causing this. Is there something else I need to do when using a join in hql to ensure the object is mapped correctly?
Edit: Added code excerpts below. I had to change all the names and attempt to extract only the relevant portions (the real code is not really about cars).
Working query:
from Car car where car.name like :name
Non-working query:
from Car car left join car.occupants occupant where car.name like :name OR (occupant.name like :oname)
Car entity:
#Entity
#Table(uniqueConstraints = {#UniqueConstraint(columnNames = {"someId"}),
#UniqueConstraint(columnNames = {"someOtherId"})})
public class Car extends SomeParentEntity
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, length = 64)
private String someId;
#Column(length = 64)
private String name;
// ... Many columns and mappings removed ...
#OneToMany(mappedBy = "car", fetch = FetchType.LAZY)
private List<Occupant> occupants;
// ...
}
Occupant entity:
#Entity(name = "car.Occupant")
#Table(uniqueConstraints = {#UniqueConstraint(columnNames = { "name" }) })
public class User extends SomeParentEntity
{
#ManyToOne
#JoinColumn(name = "carId", nullable = false)
private Car car;
#Column(length = 64, nullable = false)
private String name;
// ... Many mappings / columns removed ...
}
Your JOIN in the HQL is making Hibernate retrieve two types of objects. You can see this if you activate the SQL logging.
Assuming you have a X-to-X relation in your entity, the problem should go away if you change the query to use
... JOIN FETCH entity.relation ...
Why are you using explicit left join btw.
from Car car left join car.occupants occupant where car.name like :name OR (occupant.name like :oname)
I think we can simple use:
from Car car where car.name like :name or car.occupant.name like :oname
Then the query.list should give you List of object which should be casted back to List of car
+1's all around to the other answerers for your help, it is much appreciated.
However, the solution turned out to be very simple. The query just needed to have the select specified at the beginning for the join case in order to narrow the field (I assume).
So the non-working query:
from Car car left join car.occupants occupant where car.name like :name OR (occupant.name like :oname)
Becomes:
select car from Car car left join car.occupants occupant where car.name like :name OR (occupant.name like :oname)

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"

Categories