Simple hql named query that uses a inner join - java

I want to do something like this in my domain/entity object :
#Entity
#NamedQueries({
#NamedQuery(name="favouriteCats", query="from Cat c inner join on UserCat uc where uc.isFavourtie = true and uc.user = :user")
})
public final class Cat extends BaseTable
So that in my service layer I can do this :
Query query = session.getNamedQuery("favouriteCats")
query.setParameter(0, MyUser);
return query.list();
However, my syntax in HQL is incorrect - and aftern ten minutes looking at official docs I have decided to give up and ask here ... ?
My usercat table is joined like so :
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name="cat_fk", insertable=false, updatable=false)
private cat
The sql is this, it works fine at my db command prompt:
select c.*
from cat as c inner join usercat as uc on c.id = uc.cat_fk
and uc.isFavourite = 1 //bit field
and uc.user_fk = 74 //just user id
Is it just me or is the hibernate documentation rather painful, and do you find yourself often wondering whether it would be quicker just to write normal jdbc prepared statements to populate your pojos/domain objects/dto's... ?

I think this might work for you, but I am guessing your Usercat class here:
select c from Usercat as uc inner join uc.cat as c where uc.isFavourtie = true and uc.user = :user

Case Issue, Right query would be:
from Cat c inner join on Usercat uc where uc.isfavourtie = true and uc.user = :user
Note : C in Cat is capital, U in Usercat is capital where as c in Usercat is small and f in isfavourite is small.

Related

Hibernate HQL "Path expected for join!" #ManyToOne relationship

Suppose there are two entities - Owner
#Entity
#NamedQueries({
#NamedQuery(name = "Owner.findOwnerForPetId", query = "select o from Owner o inner join Pet p on o.ownerId=p.owner.ownerId where p.petId= :petId")
})
public class Owner {
#Id
#Column(name = "ownerId")
private Long ownerId;
#Column
private String name;
// scaffolding code...
}
and Pet
#Entity
public class Pet {
#Id
#Column(name = "petId")
private Long petId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ownerId")
private Owner owner;
#Column
private String name;
// scaffolding code...
}
where one Owner can have multiple pets (original class were renamed), but one Pet can only belong to one Owner. What I would like to do is find the Owner that owns a Pet that has some id, like:
select Owner.ownerId, Owner.name from Owner inner join Pet on Owner.ownerId=Pet.ownerId where Pet.petId=3;
This works fine when executed in pure SQL. However, I have tried these two queries in HQL and they both give the error Path expected for join!
select o from Owner o inner join Pet p on o.ownerId=p.owner.ownerId where p.petId= :petId
and
from Owner o join Pet p where p.petId= :petId
Note that there are no #OneToMany or Collection<Pet> pets in Owner. I would like to do it with only a #ManyToOne on the Pet side.
Any hints on what I have missed?
Try this one
select o from Pet p inner join p.owner o where p.petId= :petId
When working with HQL you must use the relations between entities not just entities
so for INNER JOIN and LEFT JOIN for example you should use the relation direct
For example next are valid queries
SELECT o FROM Pet p inner join p.owner o WHERE p.petId= :petId (same as #rathna accepted answer)
SELECT p FROM Pet p WHERE p.owner.ownerId = :ownerId
For the sake of completeness, if you need to LEFT JOIN but have the #ManyToOne attribute on the right side and thus can't specify a path, you can transform the query into a RIGHT JOIN with the same effect (i.e. not losing rows where the other table doesn't have matching rows and filtering the other table without losing null rows).
Suppose you want to get all owners that don't have pets ignoring pets named Charly:
You can't specify
SELECT o
FROM Owner o
LEFT JOIN o.pet p (<-------- ERROR) WITH p.name != 'Charly'
GROUP BY o.ownerId
HAVING count(p.petId) = 0
But you could transform this into:
SELECT o
FROM Pet p
RIGHT JOIN p.owner o WITH p.name != 'Charly'
GROUP BY o.ownerId
HAVING count(p.petId) = 0

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

Criteria API ignores JOIN

I have a simple criteria api query with a (inner) join
public void find(Category category) {
CriteriaBuilder b = getQueryBuilder();
CriteriaQuery<Product> q = createQuery();
Root<Product> root = q.from(Product.class);
Join<Product, Category> myCategory= root.join("category");
q.where(b.equal(myCategory, category));
entityManager.createQuery(q).getResultList();
}
The query works, but if I enable in persistence.xml the sql logging I can see that the query is a
SELECT * FROM product, category WHERE ...
and no
SELECT * FROM product join category on category.id = product.category ...
Any idea what the reason for this is? The where statement is very slow, so a real join would be really better.
I'm using eclipselink 2.5.1, Java EE7 and postgres
I also have a superclass from Product
#Entity
#Audit
public class SuperClass {}
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Product extends SuperClass {}
But this should be no problem?
A join is actual being perform, however the SQL used to perform the join is written in implicit join notation. The join will have the same performance as explicit join notation (notation using JOIN and ON). A "real" join is being performed, however it is just not in the explicit join notation (aka ANSI SQL 92) you expect.
You are selecting from Root with Product.class, but you need to select from join.
And I think result of you join must be mapped to some class, containing both entities Product and Category. If you like to get only product from join you can write something like this:
CriteriaBuilder b = getQueryBuilder();
CriteriaQuery<Product> q1 = createQuery();
CriteriaQuery<Product> q2 = createQuery();
Root<Product> root = q1.from(Product.class);
q2.select(root.as(Product.class)).from(root.join("category"))
entityManager.createQuery(q2).getResultList();

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

JPA #NamedQuery with two tables, possible?

I'm having a kind of dummy problem, I need to make a #NamedQuery with a join with other table, some simple thing.
But in all my #NamedQuery I'm only dealing with my mapped Object/Table.
For example in my Object/Table Mapped cars:
#NamedQuery(name = Cars.GET_AVAILABLE_CARS,
query = "select c.id from Cars c where c.status = (select d.status from CarStatus d where d.color=red)")
I'm trying to use: #SecondaryTables but no success for now.
One other thing that is working is give all things from other table as a parameter, but I don't think this will be good in performance.
Like:
#NamedQuery(name = Cars.GET_AVAILABLE_CARS, query =
"select c.id from Cars c where c.status = :" + CarStatusParam)
Any tips?
Thanks in advance
A named query can use everything that an API query can use, so can clearly do subqueries. Which implementation of JPA ?
I guess that you have something like this:
#Entity
public class Cars{
private String status;
//... something else
}
#Entity
public class CarStatus{
private String status;
private String color;
//... something else
}
if so, change this
#Entity
public class Cars{
private CarStatus status; //<--THIS!!
//... something else
}
#Entity
public class CarStatus{
private String color;
//... something else
}
and then update your NamedQuery to this:
query ="select c.id from Cars c where c.status.color = 'red'"
If I am wrong and the tables should not be related that way, then you should change your query tu use a join instead of a subquery. Something like this:
query = "select c.id from Cars c join CarStatus d where c.status = d.status and d.color = 'red'"
What you are trying to do is not a join. It is a subselect. And in terms of performance it is as (in)efficient as getting the param beforehand.
If you insist, however, to use the subselect, the JPA Query Language support it. As it supports joins.
Take a look here (at 7.11 8.11 . Subqueries).
The answer is yes it's possible. You need to make sure your columns define which table to look in. See the below code, you named query should work after that addition.
#Column(table = "SECONDARY_TABLE", name = "EXAMPLE_COLUMN_NAME")
private String example;

Categories