HQL JOIN involving multiple associations - java

I have three entities with relation UserDetails hasmany FiledTask and Task hasMany FiledTask. All I want is list of FiledTask of particular User
For UserDetails:
#OneToMany(mappedBy="user",cascade=CascadeType.ALL)
Collection<FiledTask> filedTasks = new ArrayList<FiledTask>();
And for Tasks I have
#OneToMany(mappedBy="task")
Collection<FiledTask> filedTasks = new ArrayList<FiledTask>();
And FiledTask looks like
#ManyToOne
#JoinColumn(nullable = false, name = "taskId")
private Tasks task;
#ManyToOne
#JoinColumn(nullable = false, name = "userId")
private UserDetails user;
I have tried
query = session.createQuery("from Tasks as tasks inner join tasks.filedTasks as files with files.user = :user");
query.setParameter("user", user); //user is UserDetails instance
But I am getting error clause can only reference columns in the driving table, means FiledTask can't userId for comparision?
with clause can only reference columns in the driving table [from com.akhi.app.cdm.Tasks as tasks inner join tasks.filedTasks as files with files.user = :user]

If you want the FiledTasks of a given user, then the easiest thing to do is
UserDetails user = session.find(UserDetails.class, userId);
Collection<FiledTask> filesTasks = user.getFiledTasks();
Using HQL, it would be
select f from FiledTask f where f.user = :user
If what you want is in fact the tasks of a given user, then the query would simply be
select t from Tasks t
inner join t.filedTasks filedTask
where filedTask.user = :user
Note that the entity Tasks shouldbe named Task. An instance represents a single task, and not multiple ones.

OK, looks like there is a bug in Hibernate
https://hibernate.atlassian.net/browse/HHH-2772
I think in your case what you need to do is
query = session.createQuery("from Tasks as tasks inner join tasks.filedTasks as files with files.user.id = :userId");
query.setParameter("userId", user.id);
Hope this helps somebody. I spend several hours on this.

I had the same problem using a MySql (Inno) DB plus my dialect class derived from Hibernate's MySQL5InnoDBDialect - class.
I had to override the supportsRowValueConstructorSyntax-method and let it return false as MySQLDialect (which is the base class of MySQL5InnoDBDialect) returns true.
#Override
public boolean supportsRowValueConstructorSyntax() {
return false;
}
Doing this, hibernate does not use the object reference (in your case .user) in order to create a tuple for the comparison. I hope, this will help.

Related

Hibernate not inserting into joined table because id is null

I have a many to many relationship between a User and a Trip entity with two foreign keys. I am trying to add a Trip to the User and even though there is a such an ID in my Users table, I receive the following exception:
Caused by: org.postgresql.util.PSQLException: ERROR: insert or update on table "UserTrip" violates foreign key constraint "user_id"
Detail: Key (user_id)=(1) is not present in table "User".
User side of the many to many relationship:
#ManyToMany
#JoinTable(name = "\"UserTrip\"", schema = "\"TransportSystem\"", joinColumns = {
#JoinColumn(name = "user_id") }, inverseJoinColumns = { #JoinColumn(name = "trip_id") })
private List<Trip> trips = new ArrayList<>();
Trip side of the many to many relationship:
#ManyToMany(mappedBy = "trips")
private List<User> users = new ArrayList<>();
DAO function to add a trip:
public void addTrip(int id, Trip trip) {
executeInsideTransaction(entityManager -> {
User user = entityManager.find(User.class, id);
user.getTrips().add(trip);
});
}
My little helper function to handle transactions within the same dao:
private void executeInsideTransaction(Consumer<EntityManager> action) {
EntityTransaction tx = entityManager.getTransaction();
try {
tx.begin();
action.accept(entityManager);
tx.commit();
} catch (RuntimeException e) {
tx.rollback();
throw e;
}
}
This is where I call to add the trip (don't think any more context is needed, if you wish, I can provide more.)
UserService userService = new UserService();
User user = userService.getById(1);
userService.addTrip(1, newTrip);
Things to note:
The entity is "User" but the table it is mapped to is called "Users" since in PostgreSQL the User is a reserved keyword.
I tried MERGE and REMOVE cascades and a lazy fetch type on the User side
I tried to pass the whole User object to the addTrip function and then use entityManager.merge() but then as read here on stackoverflow I decided to use entityManager.find() to load the user by id from the database directly and then add a role and commit the transaction. Unfortunately, both cases yield the same result (this exception).
Needless to say, there is a user_id = 1 in the database.
I would appreciate your input. I know there are many threads regarding this particular exception but honestly I seem unable to resolve it.
This has been resolved, to anyone wondering:
The problem comes from the fact that User is a reserved keyword in PostgreSQL. I tried multiple times to avoid problems with it, I created a Users table and mapped my User entity to Users table. So far there were no problems with it.
But once a joined table is involved, things get complicated. In my case, I had a UserTrip joined table with a many to many relationship and Hibernate is looking for a user_id from User. I found no way to explicitly tell hibernate to take Users rather than User, that's why I decided to name my table UsersTrip and everything has been resolved.
Lesson learned - avoid using words similiar to keywords where possible.

Hibernate - How to select just the foreign key value using Criteria Query without doing a Join?

So there is this similar (almost identical) question: How to select just the foreign key value using Criteria Query? but I need to avoid doing the join.
So I have two tables, one with a FK to the other
(Service)
id
name
account_id
(Account)
id
name
Service class has the FK defined this way:
#Entity
public class Service extends BaseDbEntity implements Serializable {
private static final long serialVersionUID = 1L;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Account account;
...
and what I want to do is query all the Service's that correspond to a given account, having the accountId, without doing a JOIN, because the id is already in the Service table.
Now, my criteria query looks like this:
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Service> criteria = cb.createQuery(Service.class);
Root<Service> root = criteria.from(Service.class);
criteria
.select(root)
.where(cb.equal(root.join("account").get("id"), accountId));
session.createQuery(criteria).getResultStream();
This ends up generating this query:
Hibernate: select service0_.id as id1_3_, service0_.name as name4_3_, service0_.account_id as account_id6_3_ from Service service0_ inner join Account account1_ on service0_.account_id=account1_.id where account1_.id=?
Which doesn't make sense.. it does a join on a field and then just creates a where clause for that field.
If I do:
root.get("account_id")
it just throws an exception saying the field is not available.
What is the correct way to avoid this?
Ok I just found the answer to my question:
Instead of doing
root.join("account").get("id")
I just needed to do:
root.get("account").get("id")
which avoids performing the JOIN.

Efficient JPQL query for retrieving complex entities

I'm quite the rookie with JPA/JPQL, so please excuse me if this question is not crystal clear.
I am trying to find an efficient JQPL query in order to get all records of a complex object.
(ie. represented by multiple tables, with several one-to-many relationships - see simplified example below):
class ComplexObject {
private Set< SubOject1> so1 ...
.....
#OneToMany(fetch = FetchType.LAZY)
public Set< SubOject1>...
}
class SubObject1 {
private Set< SubOject2> so2 ...
.....
#OneToMany(fetch = FetchType.LAZY)
public Set< SubOject2>...
}
I am using the following JPQL query :
select distinct CO
from ComplexObject CO
left join fetch CO.so1 SO1
left join fetch SO1.so2
The query is run on a stateless session, in order to get a de facto snapshot of the current data in the DB, which is detached from the entity manager (hence the usage of left join fetch).
Unfortunately, I've encountered 2 problems :
Since the complex object contains multiple instances of so1, and each so1 instance contains multiple instances of so2, the underlying translation to SQL queries generates a specific select query per row of the product of all the table joins - a very wasteful solution. Is there a way to reduce the number of internal select queries? (This seems like the dreaded N+1 queries problem).
The JPQL query returns a ComplexObject instance per internal SQL query on the product of all the table joins - which means multiple references to the ComplexObject instances. Why does this happen on a 'select distinct' query?
The JPA framework I am using is hibernate, and the DB is HyperSQL.
The (1) issue turned out to be related to using the p6spy logging framework, which printed out all the results from a large DB table. The logging format led to an incorrect assumption that many queries where being executed.
While trying to fine tune performance, using native queries did not appear to have better performance then using JPQL queries.
Using a Native Query also resulted in Object typed results, which required post processing.
You can use View Objects to receive only the columns what you want:
StringBuilder sb = new StringBuilder();
sb.append(" SELECT new ").append(ObjectVO.class.getName()).append("(co.someInfo1, co.someInfo2, so1.someInfo )");
sb.append(" FROM ComplexObject co ");
sb.append(" JOIN co.subOject1s so1 ");
sb.append(" LEFT JOIN so1.so2 so2 ");
sb.append(" WHERE so1.id = :idSo1 AND so2 = :someThing");
Query q = em.createQuery(sb.toString());
q.setParameter("idSo1", idSo1);
q.setParameter("someThing", someThing);
List<ObjectVO> listResult = q.getResultList();
The ObjectVO class:
public class ObjectVO {
private String info1;
private Long info2;
private String info3;
public PedidoModel(String info1, Long info2, String info3){
this.info1 = info1;
this.info2 = info2;
this.info3 = info3;
}
}

jpa query join fetch from a onetomany

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.

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.

Categories