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

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

Related

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)

Nested fetch join with JPQL and Hibernate

I am writing a JPQL query (with Hibernate as my JPA provider) to fetch an entity Company and several of its associations. This works fine with my "simple" ManyToMany associations, like so:
#Entity
#Table(name = "company")
#NamedQueries({
#NamedQuery(
name = "Company.profile.view.byId",
query = "SELECT c " +
"FROM Company AS c " +
"INNER JOIN FETCH c.city AS city " + <-- #ManyToOne
"LEFT JOIN FETCH c.acknowledgements " + <-- #ManyToMany
"LEFT JOIN FETCH c.industries " + <-- #ManyToMany
"WHERE c.id = :companyId"
)
})
public class Company { ... }
Hibernate creates a single query to fetch the above, which is good. However, my Company entity also has a many-to-many association with data stored in the intermediate table, hence why this is mapped as #OneToMany and #ManyToOne associations between three entities.
Company <-- CompanyService --> Service
These are the three entities that I have in my code. So a Company instance has a collection of CompanyService entities, which each has a relation to a Service instance. I hope that makes sense - otherwise please check the source code at the end of the question.
Now I would like to fetch the services for a given company by modifying the above query. I read in advance that JPA doesn't allow nested fetch joins or even aliases for joins, but that some JPA providers do support it, and so I tried my luck with Hibernate. I tried to modify the query as such:
#Entity
#Table(name = "company")
#NamedQueries({
#NamedQuery(
name = "Company.profile.view.byId",
query = "SELECT c " +
"FROM Company AS c " +
"INNER JOIN FETCH c.city AS city " +
"LEFT JOIN FETCH c.acknowledgements " +
"LEFT JOIN FETCH c.industries " +
"LEFT JOIN FETCH c.companyServices AS companyService " +
"LEFT JOIN FETCH companyService.service AS service " +
"WHERE c.id = :companyId"
)
})
public class Company { ... }
Now, instead of creating a single query, Hibernate creates the following queries:
#1
select ...
from company company0_
inner join City city1_ on company0_.postal_code = city1_.postal_code
[...]
left outer join company_service companyser6_ on company0_.id = companyser6_.company_id
left outer join service service7_ on companyser6_.service_id = service7_.id
where company0_.id = ?
#2
select ...
from company company0_
inner join City city1_ on company0_.postal_code = city1_.postal_code
where company0_.id = ?
#3
select service0_.id as id1_14_0_, service0_.default_description as default_2_14_0_, service0_.name as name3_14_0_
from service service0_
where service0_.id = ?
#4
select service0_.id as id1_14_0_, service0_.default_description as default_2_14_0_, service0_.name as name3_14_0_
from service service0_
where service0_.id = ?
Query #1
I left out the irrelevant joins as these are OK. It appears to select all of the data that I need, including the services and the intermediate entity data (CompanyService).
Query #2
This query simply fetches the company from the database and its City. The city association is eagerly fetched, but the query is still generated even if I change it to lazy fetching. So honestly I don't know what this query is for.
Query #3 + Query #4
These queries are looking up Service instances based on ID, presumably based on service IDs fetched in Query #1. I don't see the need for this query, because this data was already fetched in Query #1 (just as the data from Query #2 was already fetched in Query #1). Also, this approach obviously does not scale well if a company has many services.
The strange thing is that it seems like query #1 does what I want, or at least it fetches the data that I need. I just don't know why Hibernate creates query #2, #3 and #4. So I have the following questions:
Why does Hibernate create query #2, #3 and #4? And can I avoid it?
Does Hibernate support nested association fetching even though JPA doesn't? If so, how would I go about it in my case?
Is this behavior normal, or is it because what I am trying to do is just not supported, and therefore I get weird results? This would seem odd, because query #1 looks perfectly fine
Any pointers of mistakes or alternative solutions to accomplish what I want would be much appreciated. Below is my code (getters and setters excluded). Thanks a lot in advance!
Company entity
#Entity
#Table(name = "company")
#NamedQueries({
#NamedQuery(
name = "Company.profile.view.byId",
query = "SELECT c " +
"FROM Company AS c " +
"INNER JOIN FETCH c.city AS city " +
"LEFT JOIN FETCH c.acknowledgements " +
"LEFT JOIN FETCH c.industries " +
"LEFT JOIN FETCH c.companyServices AS companyService " +
"LEFT JOIN FETCH companyService.service AS service " +
"WHERE c.id = :companyId"
)
})
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
// ...
#ManyToOne(fetch = FetchType.EAGER, targetEntity = City.class, optional = false)
#JoinColumn(name = "postal_code")
private City city;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "company_acknowledgement", joinColumns = #JoinColumn(name = "company_id"), inverseJoinColumns = #JoinColumn(name = "acknowledgement_id"))
private Set<Acknowledgement> acknowledgements;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "company_industry", joinColumns = #JoinColumn(name = "company_id"), inverseJoinColumns = #JoinColumn(name = "industry_id"))
private Set<Industry> industries;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
private Set<CompanyService> companyServices;
}
CompanyService entity
#Entity
#Table(name = "company_service")
#IdClass(CompanyServicePK.class)
public class CompanyService implements Serializable {
#Id
#ManyToOne(targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
#Id
#ManyToOne(targetEntity = Service.class)
#JoinColumn(name = "service_id")
private Service service;
#Column
private String description;
}
Service entity
#Entity
#Table(name = "service")
public class Service {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#Column(length = 50, nullable = false)
private String name;
#Column(name = "default_description", nullable = false)
private String defaultDescription;
}
Fetching data
public Company fetchTestCompany() {
TypedQuery<Company> query = this.getEntityManager().createNamedQuery("Company.profile.view.byId", Company.class);
query.setParameter("companyId", 123);
return query.getSingleResult();
}
Okay, it seems like I figured it out. By setting the fetch type to FetchType.LAZY in CompanyService, Hibernate stopped generating all of the redundant queries that were basically fetching the same data again. Here is the new version of the entity:
#Entity
#Table(name = "company_service")
#IdClass(CompanyServicePK.class)
public class CompanyService implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Service.class)
#JoinColumn(name = "service_id")
private Service service;
#Column
private String description;
}
The JPQL query remains the same.
However, in my particular case with the number of associations my Company entity has, I was getting a lot of duplicated data back, and so it was more efficient to let Hibernate execute an additional query. I accomplished this by removing the two join fetches from my JPQL query and changing my query code to the below.
#Transactional
public Company fetchTestCompany() {
TypedQuery<Company> query = this.getEntityManager().createNamedQuery("Company.profile.view.byId", Company.class);
query.setParameter("companyId", 123);
try {
Company company = query.getSingleResult();
Hibernate.initialize(company.getCompanyServices());
return company;
} catch (NoResultException nre) {
return null;
}
}
By initializing the companyServices association, Hibernate executes another query to fetch the services. In my particular use case, this is better than fetching a ton of redundant data with one query.
I hope this helps someone. If anyone has any better solutions/improvements, then I would of course be happy to hear them.
From what you wrote, I would say that nested fetching isn't supported. This is my understanding of your results:
Query #1 is ok, and joins everything that it needs, this is good
However, Query #2 I think gets CompanyService#company (with eager city resulting in inner join City)
Query #3 gets CompanyService#service
Query #4 is a mistery to me
I know this is not an answer, but it might help you understand what's going on in the background.

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

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

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;

Categories