Hibernate warn durring query - left join fetch with Pageable - java

I have two classes wiht relation one to many. User:
#Entity
public class User {
#Id
private Long id;
...
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private Set<Note> notes = new HashSet<>();
...
}
and Note:
#Entity
public class Note {
#Id
private Long id;
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
...
}
In the UserRepository I want to override findAll(Pageable<T> va1) method from PagingAndSortingRepository<T, ID> to avoid N+1 query problem. Everything works fine, with this code:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
#Override
#Query(value = "select distinct u from User u left join fetch u.notes")
Page<User> findAll();
}
But when I add pagination:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
#Override
#Query(value = "select distinct u from User u left join fetch u.notes",
countQuery = "select count(u) from User u")
Page<User> findAll(Pageable page);
}
I see warn in console:
HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
My question is, how to fix it?

When you want to join fetch child with pagination, Hibernate do SQL query without pagination means fetch full resultset. And do pagination in memory.
The easier way to fix this using two query
Fetch ids of user only with pagination
Then user join fetch notes with IN query by those ids
Code Example
#Query("select u.id from User u")
List<Long> getAllIds(Pageable page);
#Query(value = "select distinct u from User u left join fetch u.notes where u.id IN (:ids)")
List<User> findAll(#Param("ids")List<Long> ids);

This is a perfect use case for Blaze-Persistence.
Blaze-Persistence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. The pagination support it comes with handles all of the issues you might encounter.
It also has a Spring Data integration, so you can use the same code like you do now, you only have to add the dependency and do the setup: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-setup
Blaze-Persistence has many different strategies for pagination which you can configure. The default strategy is to inline the query for ids into the main query. Something like this:
select u
from User u
left join fetch u.notes
where u.id IN (
select u2.id
from User u2
order by ...
limit ...
)
order by ...

Related

How to perform the "in" query on jpa's #ManyToOne JoinColumn without querying the entity

I have an entity like:
#Entity
class Blog{
#Id
private Long id;
// ...
#ManyToOne
#JoinColumn(name = "author_id")
private User author;
}
And I want to perform an "in" query on the author column, so I wrote my BlogRepository like:
public interface BlogRepository extends JpaRepository<Blog, Long>, CustomizedBlogRepository {
Page<Blog> findByUserIn(Collection<User> users, Pageable pageable);
}
This works, however, I need to perform two queries for one request, that is to query the User entity from UserRepository to get Collection<User> users.
Because in many situation, all I want is semantic like:
select * from blog where author_id in (some_id_list);
So is there anyway in jpa to let me perform query like below without querying the User entity?
The Order part of your method gets in the way. Since you don't want the results ordered, you can use this:
public interface BlogRepository extends JpaRepository<Blog, Long>, CustomizedBlogRepository {
Page<Blog> findByUser_IdIn(Collection<Long> userId, Pageable pageable);
}
Yes you can also write custom JPQL
public interface BlogRepository extends JpaRepository, CustomizedBlogRepository {
#Query("select b from Blog b LEFT JOIN b.author.authorId in :authorIds")
Page<Blog> getByUsersByIds(List<Integer> authorIds, Pageable pageable);
}
Here you can change authorId to any Id of User table, which you have created.And you can also try JOIN instead of LEFT JOIN

Spring Data JPA: query ManyToMany

I have entities User and Test
#Entity
public class User {
private Long id;
private String userName;
}
#Entity
public class Test {
private Long id;
#ManyToMany
private Set<User> users;
}
I can get all tests by User entity:
public interface TestRepository extends JpaRepository<EventSettings, Long> {
List<Test> findAllByUsers(User user);
}
But which query can I use for finding all tests by userName?
The following method signature will get you want to want:
List<Test> findByUsers_UserName(String userName)
This is using the property expression feature of Spring Data JPA. The signature Users_UserName will be translated to the JPQL x.users.userName. Note that this will perform an exact match on the given username.
Other answer shows how to achieve desired functionality using function naming technique. We can achieve same functionality using #Query annotation as follows:
#Query("select t from Test t join User u where u.username = :username")
List<Test> findAllByUsername(#Param("username")String username);
I was using #JoinTable and I got it working with this :
#Query("select t from Test t join t.users u where u.username = :username")
List<Test> findAllByUsername(#Param("username") String username);
t.users u instead of User u

How to define JPA Repository Query with a Join?

I would like to make a Join query by Jpa repository by annotation #Query I have three tables.
The native query is:
select application.APP_ID
from user, customer, application
where user.USE_CUSTOMER_ID = customer.CUS_ID
and application.APP_CUSTOMER_ID = customer.CUS_ID
and user.USE_ID=1;
Now I have Table Hibernate entity, so I tried in ApplicationRepository
#Query(SELECT application FROM Application a
INNER JOIN customer c ON c.customer.id = a.customer.id
INNER JOIN user u ON u.customer.id = c.customer.id
INNER JOIN application a ON a.user.id = u.id
WHERE
u.id = :user.id)
List<Application> findApplicationsByUser(#Param("User") User user);
The log says
unexpected token
Any ideas, please?
My table Entity
Application.java:
#Entity
#Table
public class Application extends BaseSimpleEntity {
...
#ManyToOne(optional = false)
private Customer customer;
...
}
Customer.java:
#Entity
#Table
public class Customer extends BaseSimpleEntity {
...
#OneToMany(mappedBy = "customer")
private List<User> users;
#OneToMany(mappedBy = "customer")
private List<Application> applications;
...
}
User.java:
#Entity
#Table
public class User extends BaseSimpleEntity {
...
#ManyToOne(optional = false)
private Customer customer;
...
}
You don't need ON clauses in JPA, because the JPA already know how entities are associated thanks to the mapping annotations.
Moreover, you're selecting application, which is not an alias defined in your query.
And your joins make no sense.
The query should simply be
select application FROM Application a
join a.customer c
join c.users u
where u.id = :userId
Read the Hibernate documentation to understand how HQL and joins work.

How to retrieve specific columns in Paginated way using Spring Data JPA?

I have One entity class, its service and repository as follows:
#Entity
#Table(name = "user")
public class User implements Serializable{
#Id
#Column(name = "id", unique = true)
private String userId;
#Column(name = "user_name")
private String userName;
#Column(name = "emp_code")
private String empCode;
// ... other properties
}
Repository
#Repository
public interface UserRepository extends PagingAndSortingRepository<User, String>
{
// .... working
#Query("select u.userName from User u")
Page<User> findAllUserName(Pageable pageable);
//... not working
#Query("select u.userName, u.empCode from User u")
Page<User> findAllUserNameAndEmpCode(Pageable pageable);
}
When I am trying to execute findAllUserName it works properly. but when using findAllUserNameAndEmpCode.. it throws following exceptions while starting tomcat:
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE, found ',' near line 1, column 29 [select count(u.userName,u.empCode) from com.entity.User u]
at org.hibernate.hql.internal.ast.QuerySyntaxException.convert(QuerySyntaxException.java:54)
at org.hibernate.hql.internal.ast.QuerySyntaxException.convert(QuerySyntaxException.java:47)
at org.hibernate.hql.internal.ast.ErrorCounter.throwQueryException(ErrorCounter.java:79)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:278)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:182)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:138)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:105)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:80)
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:168)
at org.hibernate.internal.AbstractSessionImpl.getHQLQueryPlan(AbstractSessionImpl.java:221)
at org.hibernate.internal.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:199)
at org.hibernate.internal.SessionImpl.createQuery(SessionImpl.java:1778)
at org.hibernate.ejb.AbstractEntityManagerImpl.createQuery(AbstractEntityManagerImpl.java:291)
... 63 more
I dont know why, and how its converting this query to SELECT count(..) ? What is meaning of expecting CLOSE, found ',' ??
Please help.. Thanks
You should specify the count query. The Page return value of your select function needs to know how many results there will be. So it sends a COUNT query that is probably made from your select query and looks like this:
select count(u.userName,u.empCode) from com.entity.User u
which is wrong because COUNT function takes only one parameter. So you should create your custom count query (probably like this):
select count(u.userName) from com.entity.User u
and place it into #Query annotation:
#Query(
value = "select u.userName, u.empCode from User u",
countQuery = "select count(u.userName) from com.entity.User u"
)
Page<User> findAllUserNameAndEmpCode(Pageable pageable);

TypedQuery with ManyToMany relations

I have a problem to create query with TypedQuery interface, NamedQuery and many-to-many relationship.
Here is my Report entity:
#Entity
#Table(name = "REPORT")
#NamedQueries({
#NamedQuery(name = Report.NAMED_QUERY.FIND_USERS, query = "SELECT r.users FROM Report r WHERE r = :report")})
public class Report {
public interface NAMED_QUERY {
String FIND_USERS = "Report.findUsers";
}
#ManyToMany
#JoinTable(name = "REPORT_USER", joinColumns = #JoinColumn(name = "REPORT_ID"), inverseJoinColumns = #JoinColumn(name = "USER_ID"))
private Set<User> users;
//another fields, getters and setters
}
And User Entity. Here i have no field that maps many-to-many relation.
#Entity
#Table(name = "USER")
public class User {
//fields, getters and setters
}
I have no idea how to use this named query.
public List<User> findUsersRelatedToReport(Report report) {
TypedQuery<User> query = entityManager.createNamedQuery(Report.NAMED_QUERY.FIND_USERS, User.class)
.setParameter("report", report);
return query.getResultList();
}
In the end I have exception:
Type specified for TypedQuery [package_name.User] is incompatible with query return type [interface java.util.Set]
Any help would be appreciated.
You cannot use collection valued attributes (in JPA specification terminology: collection_valued_path_expression) in SELECT.
That's why query is bit more complex, one way is following:
SELECT DISTINCT(u)
FROM User u
WHERE EXISTS (
SELECT r
FROM Report r
WHERE r = :report AND u MEMBER OF r.users)
Try changing data type of Users in your report class to List.
private List<User> users;
instead of
private Set<User> users;
You are trying to return set of users as a column in your select that is causing the error.
I think you could try to select data from User table. Try something like that:
SELECT u FROM User u WHERE u.report = :report
It's an old question, but I've also hit this recently and came up with more elegant solution: it's true you cannot use collection_valued_path in select expression, but you definitely can do joins over this path:
SELECT u FROM Report r JOIN r.users u where r = :report

Categories

Resources