Hibernate: Pagination with setFirstResult and setMaxResult - java

I'm working on a Java EE project that uses Hibernate as ORM framework.
In order to paginate the results of queries, I'm using the .setFirstResult and .setMaxResult methods (Criteria API).
The problem is that the first page is displayed correctly but when I go to page 2, I have the first result displayed equal as the last result of page one.
By switching the logging level to debug I've managed to catch the SQL query that Hibernate builds. They are:
-- First page query (results from 1 to 10)
select * from ( select this_.DT_FINE_VAL as DT1_5_0_, this_.DT_INI_VAL as DT2_5_0_, this_.CD_TIPO_PERIODO as CD3_5_0_, this_.DT_AGGIORNAMENTO as DT4_5_0_, this_.DT_INSERIMENTO as DT5_5_0_, this_.CD_USERID_AGG as CD6_5_0_, this_.CD_USERID_INS as CD7_5_0_ from GPER0_POVS2.T_POVS2_PERIODI_FUNZ this_ order by this_.CD_TIPO_PERIODO desc ) where rownum <= 10;
-- Second page query (results from 11 to 20)
select * from ( select row_.*, rownum rownum_ from ( select this_.DT_FINE_VAL as DT1_5_0_, this_.DT_INI_VAL as DT2_5_0_, this_.CD_TIPO_PERIODO as CD3_5_0_, this_.DT_AGGIORNAMENTO as DT4_5_0_, this_.DT_INSERIMENTO as DT5_5_0_, this_.CD_USERID_AGG as CD6_5_0_, this_.CD_USERID_INS as CD7_5_0_ from GPER0_POVS2.T_POVS2_PERIODI_FUNZ this_ order by this_.CD_TIPO_PERIODO desc ) row_ where rownum <= 20) where rownum_ > 10;
It seems that the second query is "wrong".
I'm using Oracle as DBMS.
Could this be an Hibernate bug? Can someone help me?
Thanks.
EDIT:
This is the code:
Session currentSession = getCurrentSession();
Criteria criteria = currentSession.createCriteria(PeriodoFunz.class);
criteria.setResultTransformer(Criteria.ROOT_ENTITY);
Order order = paginationInfo.isAsc() ? Order.asc(paginationInfo.getOrderBy()) : Order.desc(paginationInfo.getOrderBy());
criteria.addOrder(order);
....
criteria = criteria.setFirstResult(paginationInfo.getFromRecord()).setMaxResults(paginationInfo.getPageSize());
List<PeriodoFunz> result = criteria.list();

It seems that your order criteria leads to a SQL query that is not stable (returns the same result rows in different order for the queries).
You can circumvent this by adding a second order criteria for a unique attribute, e.g. the ID:
Order order = paginationInfo.isAsc() ? Order.asc(paginationInfo.getOrderBy()) : Order.desc(paginationInfo.getOrderBy());
criteria.addOrder(order);
Order orderById = paginationInfo.isAsc() ? Order.asc("id") : Order.desc("id");
criteria.addOrder(orderById);

Related

Hibernate specification not using parameters sql server

I have written a custom predicate that I make use of in a JpaSpecificationExecutor query. The generated SQL for this query does not make use of a parameter, and therefore the query cache has 2 entries (as the queries differ by 1 character!). Is the use of the aggregate function what is causing the difference I am seeing? (outlined below).
My application is using sqlserver 2012 for its database and I have monitored the queries via sqlserver management studio. The output of which I have observed making use of parameters for billingType and recordedDate but not recordedValue.
Below is the predicate code I have used:
Subquery<Entity> subQuery = query.subquery(Entity.class);
Root<Entity> subQueryRoot = subQuery.from(Entity.class);
subQuery.select(subQueryRoot.get("userId"));
Optional<Predicate> teamEquals = // Call to helper
Predicate isMinutes = builder.equal(subQueryRoot.get("billingType"), BillingType.MINUTES);
Predicate minutesDate = builder.greaterThan(subQueryRoot.get("recordedDate"), LocalDate.now().minus(Period.parse(params.getHoursPeriod())));
Predicate minutesThreshold = builder.greaterThan( subQueryRoot.get("recordedValue"), params.getHours() * 60 );
Predicate minutesRestriction = builder.and(isMinutes, minutesDate, minutesThreshold);
Predicate isDocuments = builder.equal(subQueryRoot.get("billingType"), BillingType.DOCUMENTS);
Predicate documentsDate = builder.greaterThan(subQueryRoot.get("recordedDate"), LocalDate.now().minus(Period.parse(params.getDocumentsPeriod())));
Predicate documentsThreshold = builder.greaterThan( subQueryRoot.get("recordedValue"), params.getDocuments() );
Predicate documentsRestriction = builder.and(isDocuments, documentsDate, documentsThreshold);
subQuery.where( builder.and(teamEquals.get(), builder.or( minutesRestriction, documentsRestriction ) ) );
return Optional.of(subQuery);
This results in 2 generated queries differing by 1 character.
E.g. (IN HQL)
SELECT * FROM User where id IN (SELECT userId FROM Entity WHERE billingType = #p1 AND recordedDate > #p2 AND recordedValue > 0)
vs.
SELECT * FROM User where id IN (SELECT userId FROM Entity WHERE billingType = #p1 AND recordedDate > #p2 AND recordedValue > 40)
This sounds like it is the following Hibernate bug https://hibernate.atlassian.net/browse/HHH-9576
Also there appears to be a workaround documented at Why Hibernate inlines Integer parameter list passed to JPA Criteria Query?

Select Top 1 records from MS SQL using Spring Data JPA

I am using the below #Query annotation to get the first few record from MS-SQL. It's showing error saying "< operator > or AS expected..."
#Query("SELECT Top 1 * FROM NEVS010_VEH_ACTV_COMMAND C WHERE C.EVS014_VIN = :vin ORDER BY C.EVS010_CREATE_S DESC")
CommandStatus findCommandStatusByVinOrderByCreatedTimestampDesc(#Param("vin") String vin);
You can also use findFirst and findTop as mentioned in the Docs:
findFirstByVinOrderByCreatedTimestampDesc(String vin)
Since the query is SQL (and not JPQL) one needs to set nativeQuery = true in the annotation:
#Query(nativeQuery = true, value = "SELECT Top 1 * FROM NEVS010_VEH_ACTV_COMMAND C WHERE C.EVS014_VIN = :vin ORDER BY C.EVS010_CREATE_S DESC")
CommandStatus findCommandStatusByVinOrderByCreatedTimestampDesc(#Param("vin") String vin);
For custom Queries without using nativeQuery, the field ROWNUM can be used.
Ex (in Kotlin but the same idea works in Java):
#Query("""
SELECT a
FROM Account a
WHERE a.bla = :ble
AND ROWNUM = 1
ORDER BY a.modifiedDate DESC
""")
fun findWhatever(#Param("ble") someParam: String)
I haven't found that on any doc so far. I just tested and it worked for Oracle, MySQL and H2

Data is not ordered correctly with pagination: Sql server and Hibernate 3.6.10

EDIT: the problem is related to the presence of subqueries in the select part of the translated query. This seems to mess with the numbering given by
the pagination.
We recently changed dialect from SqlServerDialect to SqlServer2008Dialect in out hibernate configuration and hibernate it's starting to translate the queries that are paginated with this sql server query:
WITH query AS (select ROW_NUMBER() OVER (order by this_.numero asc) as __hibernate_row_nr__,
this_.id as id834_0_, this_.numero as numero834_0_, this_.ragione_sociale as ragione9_834_0_, this_.anagrafica_fiscale as anagrafica10_834_0_,
this_.partita_iva as partita11_834_0_, this_.codice_fiscale as codice12_834_0_, this_.note as note834_0_, this_.data_prospect_dal as data14_834_0_,
this_.dt_inserimento as dt15_834_0_, this_.dt_modifica as dt16_834_0_, this_.nome as nome834_0_, this_.cognome as cognome834_0_,
this_.dt_nascita as dt19_834_0_, this_.fl_piva_duplicata as fl20_834_0_, this_.mail_cliente as mail21_834_0_, this_.fl_capogruppo as fl22_834_0_,
this_.fk_e2_crm_prospects as fk26_834_0_, this_.fl_duplicato as fl23_834_0_, this_.fl_a_consumo as fl4_834_0_,
this_.codice_importazione as codice24_834_0_, this_.codice_attivita as codice38_834_0_, this_.fk_e0_utente_inserimento as fk5_834_0_,
this_.fk_e0_utente_modifica as fk6_834_0_, this_.fk_e0_prof_nodo as fk7_834_0_, this_.fk_e0_decod_classe_dim_pr as fk27_834_0_,
this_.fk_e0_decod_tipo_prospect as fk28_834_0_, this_.fk_e0_decod_codice_ateco as fk29_834_0_, this_.fk_comune_di_nascita as fk30_834_0_,
this_.fk_e0_decod_gp_ind_com as fk31_834_0_, this_.fk_e0_decod_seg_com as fk32_834_0_, this_.fk_e0_decod_sotto_seg_com as fk33_834_0_,
this_.fk_e0_natura_cliente as fk34_834_0_, this_.fk_e0_nazione_nascita as fk36_834_0_, this_.fk_e0_decod_prov_pros as fk35_834_0_,
this_.fk_e2_com_punto_stradario as fk37_834_0_, replace(replace(replace(this_.ragione_sociale,'.',''),'-',''),' ','') as formula1662_0_,
( select u.user_logged from users u join e2_crm_dett_prospects p on u.id = p.fk_e0_decod_gestore_prospect
where p.fk_e2_crm_prospect = this_.id and p.fk_e0_prof_nodo = this_.fk_e0_prof_nodo) as formula1663_0_,
( select s.descrizione from e0_decod_tipi_stato_prospect s join e2_crm_dett_prospects p on s.id = p.fk_e0_decod_stato_prospect
where p.fk_e2_crm_prospect = this_.id and p.fk_e0_prof_nodo = this_.fk_e0_prof_nodo) as formula1664_0_,
( select i.num_dipendenti from e2_crm_info_camerali_prosp i where i.fk_e2_crm_prospect = this_.id
and i.dt_inserimento = ( select max(p.dt_inserimento)
from e2_crm_info_camerali_prosp p
where p.fk_e2_crm_prospect = this_.id ) ) as formula1665_0_
from e2_crm_prospects this_ where this_.fl_duplicato=? )
SELECT * FROM query WHERE __hibernate_row_nr__ BETWEEN ? AND ?
The problem is that the resulting data is not ordered by the numero column.
Using the old dialect the paginated query was translated with a TOP clause with an ORDER BY numero at the end of the query and the rows where ordered correctly.
The query is build using criteria and ordered and paginated like this:
Criteria criteria = session.createCriteria(ProspectRicerca.class,"padre");
criteria.add(Restrictions.eq(Constant.PROSPECT_FLAG_DUPLICATO, false));
criteria.addOrder(Order.asc("numero"));
criteria.setFirstResult(pageNumber*numElementForPage);
criteria.setMaxResults(numElementForPage);
lista = criteria.list();
The hibernate version is 3.6.10 and we use Sql Server 2008
This is the result of the query executed in sql server, as you can see the rows are not ordered (the __hibernate_row_nr__ column is coherent with the numero column order.

Criteria Builder select from select

Can anybody help me to create JPA Criteria Builder query in order to achieve this ?:
select id from (
select distinct r.id
r.date
r.name
from report r
inner join unit u
on u.report_id = r.id
order by
r.date desc,
r.name asc)
where rownum <= 10
I just can create inner query:
CriteriaQuery<Object[]> innerQuery = cb.createQuery(Object[].class);
Root<ReportEntity> root = innerQuery.from(ReportEntity.class);
List<Ojbect[]> resultLsit = em.createQuery(
innerQuery.multiselect(root.get(ReportEntity_.id),
root.get(ReportEntity_.date),
root.get(ReportEntity_.name)
.distinct(true)
.orderBy(cb.desc(root.get(ReportEntity_.date)),
cb.asc(root.get(ReportEntity.name))
).setMaxResults(10).getResultList();
Thx in advance :)
I've decided to get List of Object[] and then retrieve id from array
List idList = resultList.stream().map(array -> (Long)array[0]).collect(Collectors.toList());
This is code smell, but unfortunatelly I haven't found better solution.
Note I use this approach to cope Hibernate issue :
"Warning “firstResult/maxResults specified with collection fetch; applying in memory!”? - this warning pops up due to using fetch and setMaxResults in hql or criteria query.
That's why first of all I get all id, and then I find all entities according this id. (select * from ReportEntity r where r.id in :idList) - smth like this.

criteria query ORDER BY yields error. Is this an SQL-SERVER limitation? How could I order by correctly on a complicated criteria query?

I have the following criteria query:
String cat = "H";
Criteria criteria = currentSession().createCriteria(this.getPersistentClass()).
add(Restrictions.ne("category", cat)).
createAlias("employees", "emp").
createAlias("emp.company", "company");
Disjunction disjunction = Restrictions.disjunction();
for(Region r: regions){
disjunction.add(Restrictions.eq("company.region", r));
}
criteria.add(disjunction);
if(status != null) {
criteria.add(Restrictions.eq("status", status));
}
if (period != null) {
criteria.add(Restrictions.eq("period", period));
}
criteria.setProjection(Projections.groupProperty("id")) //this line was added to try to "fix" the error, but it still happened.
criteria.addOrder(Order.asc("id"));
I guess a query that explains my criteria query could be:
select n.* from NOMINATION n
join NOMINEE i on n.NOM_ID = i.NOM_ID
join EMPLOYEE e on e.EMP_ID = i.EMP_ID
join COMPANY c on c.COMPANY_CODE = e.COMPANY_CODE
where n.CATEGORY_CODE!='H' and (c.REGION_ID = ? or c.REGION_ID = ? or c.REGION_ID = ?) and n.STATUS_ID = ? and n.PERIOD_ID = ?
order by n.NOM_ID
What I am trying to do here, is pretty confusing but for the most part it works except when I add this specific line (though the query works fine):
criteria.addOrder(Order.asc("id"));
and then I get error:
java.sql.SQLException: Column "NOMINATION.NOM_ID" is invalid in the ORDER BY clause because it is not contained in either an aggregate function or the GROUP BY clause.
Which I suspect is something that has to do with SQL-SERVER. I am already grouping by id. So what am I doing wrong here, or should I just use HQL?
Your current query seems to be a simple Query which doesn't have any group function used or not a group by query. According to your current requirements you do not have to use this line.
criteria.setProjection(Projections.groupProperty("id")).addOrder(Order.asc("id"));
Or you have to modify your sql statements.

Categories