JPQL - left join with count for many in one-to-many - java

Spent about 2 hours trying to understand why JPQL query doesn't return me what I expect. Please consider the code:
System.out.println("JPQL ----------------------------");
Query q = em.createQuery(
"select u.userName, count(p.id) from User u " +
"left join u.posts p group by u.userName");
List x = q.getResultList();
for(Object o : x) {
Object[] y = (Object[])o;
System.out.printf("%s %s\n", y[0], y[1]);
}
System.out.println("Spring Data JPA -----------------");
for(User user : userRepository.findAll()) {
List<Post> posts = postRepository.findAllByAuthor(user);
System.out.printf("%s %s\n", user.getUserName(), posts.size());
}
Output is:
JPQL ----------------------------
user1 0
user2 0
Spring Data JPA -----------------
user1 3
user2 10
I expect JPQL approach to print the same as what repository approach does. Where's the mistake?
Update
Here's what SQL trace says:
select
user0_.userName as col_0_0_,
count(post2_.id) as col_1_0_
from User user0_
left outer join User_Post posts1_
on user0_.id=posts1_.User_id
left outer join Post post2_
on posts1_.posts_id=post2_.id
group
by user0_.userName

Query was correct, but there was issue with relationships definition. Here's what I had:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
...
#OneToMany
private List<Post> posts;
...
}
#Entity
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
...
#ManyToOne
private User author;
...
}
It appeared that I have to specify relationship between User and Post like this:
#OneToMany(mappedBy = "author")
private List<Post> posts;

Related

how to select multiple columns in subquery(hql)

Is there a way to select multiple columns in a subquery of hql?
I want to convert the following mysql query to hql
select u.name, sub.cnt from user u
inner join (select user_id, count(user_id) cnt from user_log group by user_id order by cnt desc limit 5) sub
on u.id = sub.user_id;
But in hql, I know that can use subquery only in where clause
I'm not aware of a pure Hibernate solution. I talk about that in my Blog post.
But there is a FluentJPA solution that you may consider:
FluentQuery query = FluentJPA.SQL((User u) -> {
UserIdCount sub = subQuery((UserLog log) -> {
int count = alias(COUNT(log.getUserId()), UserIdCount::getCount);
SELECT(log.getUserId(), count);
FROM(log);
ORDER(BY(count).DESC());
LIMIT(5);
});
SELECT(u.getName(), sub.getCount());
FROM(u).JOIN(sub).ON(u.getId() == sub.getUserId());
});
return query.createQuery(em, UserNameCount.class).getSingleResult();
which produces the following SQL:
SELECT t0.name, q0.count
FROM USER t0 INNER JOIN (SELECT t1.user_id, COUNT(t1.user_id) AS count
FROM USER_LOG t1
ORDER BY COUNT(t1.user_id) DESC
LIMIT 5 ) q0 ON (t0.id = q0.user_id)
Entities I used (declared with lombok):
#Entity
#Getter
#Table(name = "USER")
class User {
private Long id;
private String name;
}
#Entity
#Getter
#Table(name = "USER_LOG")
class UserLog {
private Long userId;
}
#Tuple
#Getter
class UserIdCount {
private Long userId;
private int count;
}
#Tuple
#Data
class UserNameCount {
private int count;
private String name;
}

Need help translating a MYSQL DB Sql Query into a Jpql query

I have 3 entities - usergroup, user, usergroupmemberships. usergroupmemberships has a 1 to 1 to both user and usergroup. im trying to get a users groups with his id
The following Query works in my DB:
SELECT *
FROM UserGroup g
JOIN UserGroupMembership ON
UserGroupMembership.usergroup_id = g.id
WHERE UserGroupMembership.user_id = 868
Wildfly:
the same as a Java REST POST that uses a query from my entitymanager doesnt with a nullpointerexception
SELECT g
FROM UserGroup g
JOIN UserGroupMembership
ON UserGroupMembership.usergroup = g.id
WHERE UserGroupMembership.user = :id
How do i write this correctly?
Tried different ways to write the query and tried writing user_id
im on wildfly and java ee
public List<UserGroup> findUserGroupbyUser(long id) {
return em.createQuery("SELECT g FROM UserGroup g JOIN
UserGroupMembership ON UserGroupMembership.usergroup = g WHERE
UserGroupMembership.user.id = :id", UserGroup.class).setParameter("id",
id).getResultList();
}
#Entity
public class UserGroupMembership {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(cascade = CascadeType.ALL)
private UserGroup usergroup;
#OneToOne(cascade = CascadeType.ALL)
private User user;
expected result is 1 entry

How to filter select from table by another table by exclusion principle

My application under Spring Boot v1.5.7
I have 3 entities (schematically):
#Entity
public class Word {
#Id
#GeneratedValue
private Integer id
...
}
#Entity
public class UserWordList {
#Id
#GeneratedValue
private Integer id
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "word_id")
private Word word;
}
#Entity
public class UserAnotherWordList {
#Id
#GeneratedValue
private Integer id
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "word_id")
private Word word;
}
And now I need to select all Words for User, but exclude Words placed in user's lists
Native SQL for user_id=1 is
select *
from Word w
left join UserWordList uwl
on w.id = uwl.word_id and uwl.user_id = 1
left join UserAnotherWordList uawl
on w.id = uawl.word_id and uawl.user_id = 1
where uwl.word_id is NULL
and uawl.word_id is NULL
What is a best way to do it? Ideally I would like to use Spring Data features or HQL, but I don't understand how...
UPD
I solve my problem with native query:
#Entity
#NamedNativeQuery(
name = "User.getWordsToProcess",
resultClass = Word.class,
query = "<...native query to select Words...>"
)
public class User {...}
...
public interface UserRepository extends CrudRepository<User, Integer> {
List<Word> getWordsToProcess(Integer userId);
}
Fastest answer is Criteria api (but that is deprecated in hibernate 5.2 and above.)
So you can use Hql :
getSession().createQuery(" select * from UserWordList u left join fetch u.word
left join fetch u.user").list()
And you can use union or create another query to fetch UserAnotherWordList.
Also you can set any restrictions in Hql like below:
Query query = getSession().createQuery(" select * from UserWordList u left join fetch u.word left join fetch u.user us where us.user = :sample").list();
query.setParameter("sample",value);
query.list();

Criteria Add all the tables from the entity class when it only needs part of it

when i create a count query with hibernate - Criteria - add all the possible table from the entity class as left join which is bad performance .
The entity :
#Entity
#Table(name = "employees")
Public Class Employees {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "lz_job_stat_id")
private Integer id;
#ManyToOne
#JoinColumn(name = "departments_id")
private Departments departments;
#ManyToOne
#JoinColumn(name = "managers_id")
private Managers managers;
}
And the criteria :
public class EmployeeDao {
public List<EmpDao> findIt(){
.....
Criteria crit = createEntityCriteria().setFetchMode("departments", FetchMode.SELECT);
crit.add(Restrictions.eq("managers.deleted", false));
crit.setProjection(Projections.count("id"));
return crit.list();
}
}
And the produced SQL :
select count() as y0_
from employees this_
left outer join departments department3_
on this_.department_id=department3_.department_id
left outer join managers manager2_
on this_.manager_id=manager2_.manager_id
now when i try the crit.list - it create a left join for all the possible tables.
when its not supposed to create a join for all of them.
isnt Criteria smart enought to know i dont need this tables ? only the one i use the "WHERE CLAUSE"
is there a way to explicitly tell Criteria "DO NOT JOIN THIS TABLES !!!"
without SQL
Specify fetch type on ManyToOne annotation:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "departments_id")
private Departments departments;
or IMHO more preferably in criteria:
criteria.setFetchMode("departments", FetchMode.SELECT)

JPQL Join on one to many database

I know that it has been mentioned couple of times here before, but I really can't make it working for me,
I have two entities: Recipe, Ingredient:
#Entity
#Table(name = "Recipe")
public class Recipe {
#Id
#GeneratedValue
#Column(name = "Recipe_id")
private Long id;
private String name;
private String description;
#Lob
#Basic(fetch = FetchType.LAZY)
#Column(length = 100000)
private byte[] image;
#OneToMany(mappedBy = "recipe", fetch = FetchType.EAGER)
#Cascade({CascadeType.ALL})
private List<Ingredient> ingredientsList;
....
}
and Ingredient:
#Entity
#Table
public class Ingredient {
#Id
#GeneratedValue
private Long id;
private String name;
private int cpt;
private String cptyType;
#ManyToOne
#JoinColumn(name = "Recipe_id")
private Recipe recipe;
....
}
I have also set up JPA Reposiotries, I would like to create custom query which would be equivalent to:
SELECT *
FROM `Recipe`
INNER JOIN `Ingredient` ON Recipe.Recipe_id = Ingredient.Recipe_id
WHERE Ingredient.name = "fancyName"
LIMIT 0 , 30
Until now I've tried with this one:
#Query("Select r from Recipe r join r.id i where i.name = :ingredient")
List<Recipe> findRecipeByIngredient(#Param("ingredient") String ingredient);
End up with expcetion:
Caused by: java.lang.NullPointerException
at org.hibernate.hql.internal.ast.HqlSqlWalker.createFromJoinElement(HqlSqlWalker.java:395)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.joinElement(HqlSqlBaseWalker.java:3477)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromElement(HqlSqlBaseWalker.java:3263)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromElementList(HqlSqlBaseWalker.java:3141)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromClause(HqlSqlBaseWalker.java:694)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:550)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:287)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:235)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:248)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:183)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:136)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:101)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:80)
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:119)
at org.hibernate.internal.AbstractSessionImpl.getHQLQueryPlan(AbstractSessionImpl.java:214)
at org.hibernate.internal.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:192)
I've tried with thing such as this:
#Query("Select r from Recipe r join fetch r.ingredientsList where r.name = :ingredient")
List<Recipe> findRecipeByIngredient(#Param("ingredient") String ingredient);
this is not resulting in any errors, but returns empty result.
It is a trival issue, but I don't have any experience with jpql before = /
EDIT:
still getting empty result:
DEBUG (SqlStatementLogger.java:104) - select recipe0_.Recipe_id as Recipe1_1_0_, ingredient1_.id as id0_1_, recipe0_.description as descript2_1_0_, recipe0_.image as image1_0_, recipe0_.name as name1_0_, ingredient1_.cpt as cpt0_1_, ingredient1_.cptyType as cptyType0_1_, ingredient1_.name as name0_1_, ingredient1_.Recipe_id as Recipe5_0_1_, ingredient1_.Recipe_id as Recipe5_1_0__, ingredient1_.id as id0__ from Recipe recipe0_ inner join Ingredient ingredient1_ on recipe0_.Recipe_id=ingredient1_.Recipe_id where ingredient1_.name=?
DEBUG (CollectionLoadContext.java:224) - No collections were found in result set for role: com.bla.model.Recipe.ingredientsList
EDIT 2:
after removing fetch from statement:
DEBUG (SqlStatementLogger.java:104) - select recipe0_.Recipe_id as Recipe1_1_, recipe0_.description as descript2_1_, recipe0_.image as image1_, recipe0_.name as name1_ from Recipe recipe0_ inner join Ingredient ingredient1_ on recipe0_.Recipe_id=ingredient1_.Recipe_id where ingredient1_.name=?
DEBUG (StatefulPersistenceContext.java:899) - Initializing non-lazy collections
Your last query searches all the recipes whose name is the ingredient name passed as argument. That's not what you want. What you want is all the recipes having an ingredient whose name is the ingredient name passed as argument:
select r from Recipe r
join r.ingredientList i
where i.name = :ingredient
Side note: why can't I have two recipes using the same ingredients? Quite limiting. The association should be a ManyToMany.
Your JPQl query should look like this:-
(As JPA already baked in the join metadata but for the #OnetoMany we can use this for JPQl)
#Query("Select r from Recipe r join Ingredient i on r.id = i.recipe.id where i.name=:ingredient)
List<Recipe> findRecipeByIngredient(#Param("ingredient") String ingredient);

Categories