Hello, Team!
This ticket is related to this discussion
But the root cause looks clear now, so I created a new ticket.
The issue can be recreated on your Benchmark project
- Use the Criteria API instead of JPQL
- Increase the count of iterations
- Catch the time elapsed only for queries in the loop
@Benchmark
public void single(Blackhole bh, EventCounters counters) {
em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
final Author author = em.createQuery( "from Author", Author.class ).setMaxResults( 1 ).getSingleResult();
//add start time
long startTime = System.currentTimeMillis();
//increase count of iterations to 10000
for (int i = 0; i < 10000; i++) {
// Use Criteria API instead of JPQL
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Book> criteria = builder.createQuery(Book.class);
Root<Book> root = criteria.from(Book.class);
criteria.select(root);
List<Predicate> restrictions = new ArrayList<>();
restrictions.add(root.get("author").in(author));
criteria.where(restrictions.toArray(new Predicate[0]));
TypedQuery<Book> query = em.createQuery(criteria);
query.setHint( "org.hibernate.cacheable", "true");
final List<Book> books = query.getResultList();
for ( Book book : books ) {
if ( bh != null ) {
bh.consume( book );
}
}
if ( counters != null ) {
counters.books += books.size();
}
}
long endTime = System.currentTimeMillis();
long timeElapsed = endTime - startTime;
//show Time elapsed
System.out.println("Time elapsed: " + timeElapsed);
em.getTransaction().commit();
em.close();
}
Hibernate 5.6.15
Time elapsed: 173
Time elapsed: 83
Time elapsed: 76
Time elapsed: 74
Time elapsed: 66
Hibernate 7.2.7
Time elapsed: 618
Time elapsed: 241
Time elapsed: 206
Time elapsed: 199
Time elapsed: 189
The difference for each iteration is ~200%, which looks noticeable.
After debugging and profiling that case, we found that the reason is in this method:
org.hibernate.query.sqm.internal.SqmInterpretationsKey#isCacheable
Based on the comments, the behavior looks clear.
// for now at least, skip caching Criteria-based plans // - especially wrt parameters atm; this works with HQL because the // parameters are part of the query string; with Criteria, they're not. return keySource.isQueryPlanCacheable() // At the moment we cannot cache query plan if there is filter enabled. && !keySource.getLoadQueryInfluencers().hasEnabledFilters()
Hibernate 6 and 7 never cache the query plan
- For Criterria API
- If there is filter enabled.
There are a couple of other cases, but for us first two are important. Mainly, we use Criteria API, but as we have filter enabled - query plan is never cached for our application, even for JPQL.
In our app, in one web request, we may have dozens of small queries (sometimes > 100), so the overall time difference looks noticeable.
Is there any chance that this behavior will be changed?
Possibly, we can solve this on our side?
Thanks for any suggestions.