jpa query join fetch from a onetomany - java

I have the following 2 classes using JTA transaction type with openjpa & a derby embedded db. What I want is to get the parent Organisation & the requested WSpace in one query when I only have the WSpace id. I am quite new to JPA so am learning as I go and have been struggling with 'q2' query. I have been using queries 'q0' & 'q1' to debug and check the items do exist in the db. 'q0' returns 1 object as does 'q1', whereas 'q2' returns 0 objects
I have tried a variety of entity setups and different queries but nothing has worked yet.
Orignally the WSpace class did not have an Organisation field as it didn't seem necessary for persisting or selecting, but I added it (along with the mappedby parameter) incase it was needed for the query to work, but nothing has changed.
back to the original question how can I get this to work so it returns the parent object with the single child being requested
SELECT o FROM Organisation o JOIN FETCH o.spaces w WHERE w.id = :id
Here are my classes
#Entity
public class Organisation implements MyObjects
{
#Id
#NotNull
private Integer id;
private String name;
#OneToMany( mappedBy = "organisation",
cascade = { CascadeType.PERSIST, CascadeType.MERGE } )
private List<WSpace> spaces;
//getters/setter below
}
And
#Entity
public class WSpace implements MyObjects
{
#Id
#NotNull
private Integer id;
private String name;
#ManyToOne
private Organisation organisation;
#OneToMany
private List<Application> apps;
//getters/setter below
}
class DAO
{
...
public void foo( Integer id )
{
....
String q0 = "SELECT o FROM Organisation o WHERE o.id = 49068";
List<Organisation> res0 = em.createQuery( q0, Organisation.class ).getResultList();
String q1 = "SELECT w FROM WSpace w WHERE w.id = " + id;
List<WSpace> res1 = em.createQuery( q1, WSpace.class ).getResultList();
String q2 = "SELECT o FROM Organisation o "
+ "JOIN FETCH o.spaces w WHERE w.id = " + id;
List<Organisation> res2 = em.createQuery( q2, Organisation.class ).getResultList();
...
}
}

Have you tried to look in the logs for output of your q2 query?
I am learning JPA too and was dealing with Criteria and QL queries quite recently.
So after having pretty same problems with joins, I started checking logs and it was pretty clear, what the issues were, since logs showed up translated to SQL queries.
Another thing to look, how are you generating your Entities? I used Netbeans generating it for me. Also, many to many relations mignt have helper class generated too, I saw it in one of the projects.
Hope it helps..

The query you're looking for is probably this:
SELECT w FROM WSpace w LEFT JOIN FETCH w.organisation where w.id=:id
with query.setParameter("id", id); to bind the id parameter to the query. This effectively tells the persistence provider to fetch the WSpace.organisation relation while querying for WSpace entities in the same query. Using the LEFT [OUTER] keword (OUTER being optional) in front of the JOIN FETCH keywords tells your persistence provider to select WSpace objects even if there are no matching Organisation records for your WSpace.organisation relation.

Related

EntityNotFoundException LEFT JOIN JPA - Hibernate

Exception in thread "main" javax.persistence.EntityNotFoundException:
Unable to find CNPJ with id 00001388000307
I was reading jpa documentation, i read that this exception is thrown when it try to accesse(by the method getReference of EntityManger interface) and the entity doesn't exists
Thrown by the persistence provider when an entity reference obtained by EntityManager.getReference is accessed but the entity does not exist.
I have this entitys: Salesman e CNPJ. It's possible that exists many salesmen with the same CNPJ, in other words, a relationship #ManyToOne.
This relationship is working, OK.
But, when i try to execute the query
select r from Salesman r join fetch r.yearMonth left join fetch r.cnpj
to bring the salesmen with its yearMonth(it's working!) relationship and its CNPJ relationship, throws the Exception, when i try to do a LEFT JOIN, that i mentioned.
When i don't execute a LEFT JOIN, works great, all Salesmen with his CNPJs and nothin of Exceptions, BUUUUT, some salesmen don't have CNPJ nad i have to bring them too, there's a necessity to do a LEFT JOIN.
#Entity
public class Salesman{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#JoinColumn(name = "year_month")
#ManyToOne
private YearMonth yearMonth;
private String cnpjS;
#JoinColumn(name = "cnpj")
#ManyToOne
private CNPJ cnpj;
public AnoMes getYearMonth() {
return yearMonth;
}
public CNPJ getCnpj() {
return cnpj;
}
}
#Entity
public class CNPJ {
#Id
#Column(name = "CCG", length = 14)
private String ccg;
public String getCcg() {
return ccg;
}
}
The select generated by Hibernate:
select *
from salesman s
inner join yearmonth y on s.ano_mes = y.id
left outer join cnpj c on s.cnpjS = c.CCG
This consult returns this values, rcnpj is cnpj from Salesman and bcnpj is from CNPJ. Some Salesmen came with CNPJ NULL, i think that it's the problem. I don't think so.
Try to enable hibernate sql logging with <property name="show_sql">true</property> to see how real query (native, invoked on db) looks like, maybe there is a inner join after hibernate processing.
To change it you can try #Column(nullable=true) annotation for your relationship or #Fetch(FetchMode.SELECT) to see how real query will change.
The reason is that you have a reference to cnpj with ID 00001388000307, but such row of cnpj doesn't exist.
You can try to use #NotFound(action=NotFoundAction.IGNORE) to skip such error, but it is recommended to fix your database of handle a exception.
You are using the cnpj column on the table Salesman as both a foreign key and a value column. In that case #staszko032's suggestion using #NotFound(action=NotFoundAction.IGNORE) looks like will work as you expect
But I think you should refactor your model if possible. You could create a CNPJ entity always, in that case you won't have Salesman without a realation to CNPJ and could use INNER JOIN, or use 2 columns, one that will be used when the Salesman doesn't have a concrete related CNPJ (the value as string) and the other when it has (the foreign key)

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)

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