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

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

Related

Hibernate query with #Formula-annotated attribute in order by clause

I have 2 entities:
#Entity
class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Contract> contracts= new HashSet<>();
#Formula("(select count(m.ORDER_ID) from myschema.ORDER_CONTRACTS m where m.ORDER_ID = id)")
private Integer numberOfContracts; // this is basically contracts.size()
}
and
#Entity
class Contract {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String client;
// some other properties
}
When I want to get my orders ordered by numberOfContracts, hibernate generates this query for me
SELECT order0_.id AS id1_5_,
(SELECT COUNT(m.ORDER_ID)
FROM myschema.ORDER_CONTRACTS m
WHERE m.ORDER_ID = order0_.id) AS formula1_
FROM myschema.order order0_
ORDER BY (SELECT COUNT(m.ORDER_ID)
FROM myschema.ORDER_CONTRACTS m
WHERE m.ORDER_ID = order0_.id) DESC
and fails with
com.ibm.db2.jcc.am.SqlSyntaxErrorException: DB2 SQL Error: SQLCODE=-206, SQLSTATE=42703, SQLERRMC=ORDER0_.ID, DRIVER=4.27.25
When I replace the select in the ORDER BY with formula1_ like this:
SELECT order0_.id AS id1_5_,
(SELECT COUNT(m.ORDER_ID)
FROM myschema.ORDER_CONTRACTS m
WHERE m.ORDER_ID = order0_.id) AS formula1_
FROM myschema.order order0_
ORDER BY formula1_ DESC
I get the expected result.
Is there a way to tell hibernate to use the generated alias (formula1_) instead of replicating the formula in the order by?
EDIT:
How I get my query:
I'm using an org.springframework.web.bind.annotation.RestController. This controller offers a endpoint to get all Orders by a method like this:
#GetMapping("orders")
public List<Order> getOrders(Pageable pageable);
When I send a request like http://localhost:8080/api/orders/sort=numberOfContracts,desc&size=100&page=0
to the endpoint, the pageable contains the information about ordering. My contoller then calls my
public interface OrderRepository extends PagingAndSortingRepository<Order, Integer>
witch provides this method:
Page<Order> findAll(Pageable page);
After this point spring and hibernate do their magic.
What kind of HQL query are you using. Hibernate will just do what you tell it to do. You will have to use the HQL alias as well in the order by clause if you want the SQL alias to be used:
SELECT o.id, o.numberOfContracts as num
FROM Order o
ORDER BY num desc

Hibernate warn durring query - left join fetch with Pageable

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 ...

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

JPA Native Query select and cast object

I have got an Object Admin which extends User. By default both Objects are in the table User_ of my Derby Database (included fields from Admin). Normally I'd select an User like this:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root user= query.from(User.class);
Predicate predicateId = cb.equal(category.get("id"), id);
query.select(user).where(predicateId);
return em.createQuery(query).getSingleResult();
However due to the complexity of my query I'm using a native query like this:
Query query = em.createNativeQuery("SELECT USER.* FROM USER_ AS USER WHERE ID = ?");
query.setParameter(1, id);
return (User) query.getSingleResult();
Though this throws a cast exception. I figure this is due to any fields from Admin.
My question is, how can I select a User using a native query with an equal result as the first example (including the same values for #LOB and #ManyToOne (et cetera) as the JPQL query would return)?
You might want to try one of the following ways:
Using the method createNativeQuery(sqlString, resultClass)
Native queries can also be defined dynamically using the EntityManager.createNativeQuery() API.
String sql = "SELECT USER.* FROM USER_ AS USER WHERE ID = ?";
Query query = em.createNativeQuery(sql, User.class);
query.setParameter(1, id);
User user = (User) query.getSingleResult();
Using the annotation #NamedNativeQuery
Native queries are defined through the #NamedNativeQuery and #NamedNativeQueries
annotations, or <named-native-query> XML element.
#NamedNativeQuery(
name="complexQuery",
query="SELECT USER.* FROM USER_ AS USER WHERE ID = ?",
resultClass=User.class
)
public class User { ... }
Query query = em.createNamedQuery("complexQuery", User.class);
query.setParameter(1, id);
User user = (User) query.getSingleResult();
You can read more in the excellent open book Java Persistence (available in PDF).
───────
NOTE: With regard to use of getSingleResult(), see Why you should never use getSingleResult() in JPA.
The accepted answer is incorrect.
createNativeQuery will always return a Query:
public Query createNativeQuery(String sqlString, Class resultClass);
Calling getResultList on a Query returns List:
List getResultList()
When assigning (or casting) to List<MyEntity>, an unchecked assignment warning is produced.
Whereas, createQuery will return a TypedQuery:
public <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass);
Calling getResultList on a TypedQuery returns List<X>.
List<X> getResultList();
This is properly typed and will not give a warning.
With createNativeQuery, using ObjectMapper seems to be the only way to get rid of the warning. Personally, I choose to suppress the warning, as I see this as a deficiency in the library and not something I should have to worry about.
When your native query is based on joins, in that case you can get the result as list of objects and process it.
one simple example.
#Autowired
EntityManager em;
String nativeQuery = "select name,age from users where id=?";
Query query = em.createNativeQuery(nativeQuery);
query.setParameter(1,id);
List<Object[]> list = query.getResultList();
for(Object[] q1 : list){
String name = q1[0].toString();
//..
//do something more on
}
Please refer JPA : How to convert a native query result set to POJO class collection
For Postgres 9.4,
List<String> list = em.createNativeQuery("select cast(row_to_json(u) as text) from myschema.USER_ u WHERE ID = ?")
.setParameter(1, id).getResultList();
User map = new ObjectMapper().readValue(list.get(0), User.class);
The best solution I found is using Interface projection .
At the beginning, I created a DTO class but it just didn't work, replacing the class with an interface like this works great:
#Query(value = "SELECT vat as vatRate, SUM(...) as amount from ...", nativeQuery = true)
List<VatReportLine> getSalesVats();
public interface VatReportLine {
double getVatRate();
long getAmount();
}
First of all create a model POJO
import javax.persistence.*;
#Entity
#Table(name = "sys_std_user")
public class StdUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "class_id")
public int classId;
#Column(name = "user_name")
public String userName;
//getter,setter
}
Controller
import com.example.demo.models.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import java.util.List;
#RestController
public class HomeController {
#PersistenceUnit
private EntityManagerFactory emf;
#GetMapping("/")
public List<StdUser> actionIndex() {
EntityManager em = emf.createEntityManager(); // Without parameter
List<StdUser> arr_cust = (List<StdUser>)em
.createQuery("SELECT c FROM StdUser c")
.getResultList();
return arr_cust;
}
#GetMapping("/paramter")
public List actionJoin() {
int id = 3;
String userName = "Suresh Shrestha";
EntityManager em = emf.createEntityManager(); // With parameter
List arr_cust = em
.createQuery("SELECT c FROM StdUser c WHERE c.classId = :Id ANd c.userName = :UserName")
.setParameter("Id",id)
.setParameter("UserName",userName)
.getResultList();
return arr_cust;
}
}

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