i want to configure query cache without second level cache for some learning purpose as i have read it can be done but somehow for me query cache works only with 2nd level cache.
Here is my configuration
<property name="cache.use_query_cache">true</property>
Entity class is
#Entity
public class Company {}
but when i do
session = factory.openSession();
Query getQuery=session.createQuery("from Company where companyId=1");
getQuery.setCacheable(true);
Object company2 = getQuery.uniqueResult();
session.close();
session = factory.openSession();
getQuery=session.createQuery("from Company where companyId=1");
getQuery.setCacheable(true);
company2 = getQuery.uniqueResult();
session.close();
Two separate queries are fired .
As soon as i do
#Entity
#Cacheable
#Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Company {}
Query cache work but it enables the second level cache also. So how to configure query
cache without enabling second level cache?
Query cache needs the 2nd level cache to do its job, since the results of the queries are then retrieved from the 2nd level cache. The query cache just keeps track of a query and the set of ids that the query returned. These ids are then retrieved from 2nd level cache from further speed increase. Where did you read you could have query cache without 2nd level cache?
Related
I need to ensure that only one object of the same entity is created (as it is done when loading data using first level hibernate cache, to simplify entity refreshing) and I want to use second level cache.
Im testing ehcache but cant make it working. Normally the entities in my app are being nested in other entities but this is just an simple example of usage:
a) example using first level cache which works fine:
session = HibernateUtil.getSessionFactory().openSession();
transaction = session.getTransaction();
transaction.begin();
Person person=session.get(Person.class, 1L);
Person person2=session.get(Person.class, 1L);
transaction.commit();
session.close();
System.out.println(person2 == person);
and it returns true
b) using second level cache with Ehcache
//1. load person with id 1
session = HibernateUtil.getSessionFactory().openSession();
transaction = session.getTransaction();
transaction.begin();
Person person=session.get(Person.class, 1L);
transaction.commit();
session.close();
//2. load the same person
session = HibernateUtil.getSessionFactory().openSession();
transaction = session.getTransaction();
transaction.begin();
Person person2=session.get(Person.class, 1L);
transaction.commit();
session.close();
System.out.println(person2 == person);
and it returns false
Is it normal behaviour of second level cache to be like that or do i miss something?
Does any second level cache engine keep only one instance of the same entity (as first level cache do)?
This is not possible to keep one instance of the entity in RAM in Hibernate using second level cache as each time Hibernate find it in the cache, it just creates new instance based on the cached data.
For my purpose I have implemented AVL Tree based loaded entities and database synchronization engine that creates repositiories based on the loaded entities from hibernate and asynchronously search throught all the fields in entities and rewrites/merge all the same fields (so that if some field (pk) is the same entity like the one in repository, it replaces it)
In this way synchronization with database is easy as it comes to find the externally changed entity in the repository (so basically in the AVL Tree which is O(log n)) and rewrite its fields.
From JPA documentation I can see that the AUTO is the default flush mode, flush should happen before any query execution. I have tried this on spring boot jpa and I can see the flush won't happen on queries from different entities , is that expected ? even though different entity may have relation to it ( Department <--> Person here )
The flush should trigger before any query according to this article :
https://vladmihalcea.com/how-do-jpa-and-hibernate-define-the-auto-flush-mode/
// this triggers flush //
Person person = personRepository.findById(5L).get();
person.setName("hello test");
Person person1 = (Person) entityManager.createQuery("select person from Person
person where person.id=11").getSingleResult(); // flush before query
// this doesn't trigger flush even if the department has the person //
Person person = personRepository.findById(5L).get();
person.setName("hello test");
Department department= (Department) entityManager.createQuery("select
department from Department
department where department.id=1").getSingleResult();
Update:
I noticed the flush happens for JPQL queries on the same table only that has the DML , while for native sql queries it will always flush before any query if there is DML before. even though no flush happens the JPQL return the managed entity with modification not the one in DB. can anyone please explain if this follow JPA standard or not ?
As JPA is a specification this question is simple to answer. Check out the spec :-)
3.10.8 Queries and Flush Mode
The flush mode setting affects the result of a query as follows.
When queries are executed within a transaction, if FlushModeType.AUTO is set on the Query, TypedQuery, or StoredProcedureQuery object, or if the flush mode setting for the persistence context is AUTO (the default) and a flush mode setting has not been specified for the query object, the persistence provider is responsible for ensuring that all updates to the state of all entities in the persistence context which could potentially affect the result of the query are visible to the processing of the
query. The persistence provider implementation may achieve this by flushing those entities to the database or by some other means. If FlushModeType.COMMIT is set, the effect of updates made to entities in the persistence context upon queries is unspecified.
If the persistence context has not been joined to the current transaction, the persistence provider must
not flush to the database regardless of the flush mode setting.
package javax.persistence;
public enum FlushModeType {
COMMIT,
AUTO
}
If there is no transaction active, the persistence provider must not flush to the database
https://download.oracle.com/otn-pub/jcp/persistence-2_1-fr-eval-spec/JavaPersistence.pdf?AuthParam=1561799350_4cc62583442da694a6a033af82faf986
Then there is the Hibernate Doc:
6.1. AUTO flush
By default, Hibernate uses the AUTO flush mode which triggers a flush in the following circumstances:
prior to committing a Transaction
prior to executing a JPQL/HQL query that overlaps with the queued entity actions
before executing any native SQL query that has no registered synchronization
https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#flushing
As I have read in many articles (e.g. here) - to enable Hibernate's second level cache for given entity we need to set cache concurrency strategy on entity via #org.hibernate.annotations.Cache annotation.
#Entity
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Person {
Besides I also use query-level cache (using query.setCacheable(true)) on some queries that fetches this entity and it works well.
My question relates to custom queries that uses DTO projection, so for the queries like this:
Query query = session.createQuery("SELECT new PersonDto(person.id, person.name) FROM Person person WHERE person.name = :name");
query.setParameter("name", name);
query.setCacheable(true);
query.uniqueResult();
Do I need to set #Cache annotation also for PersonDto? I have tried to run the query without the annotation and the DTO was successfully cached.
Could you explain why do we need the annotation for entity objects only and other non-entity objects does not require that?
Thanks.
I'm not 100% on this, but you are manually setting cacheable to true for the query.
The annotation on Person is the equivalent for an entity.
I wouldn't think of it as PersonDTO being cached in this instance. If you were to write another query saying select new PersonDTO(person.id, person.name) from Person person where person.id = 10, I don't think it will look into your cache to see if a PersonDTO with id == 10 exists; whereas, the Entitys cache would because it understands they are the same thing.
I would think of it as the query itself is being cached (meaning if ran again before TTL then cached results would occur). It's caching the fact that you ran this query with a certain name parameter, not that a PersonDTO with that name exists in the cache. Does that make sense?
I am using Hibernate 3.2.5 for my application. I am trying to implement Query Cache but it is not working.
Problem Description:
For the first time, the DB is hit, data is fetched and cached. For the second time, for the same query again the DB is hit rather than taking the data from the cache. For the second time, I want it to take it from the cache rather than hitting the DB again.
For enabling the Query Cache, I made the below entries in the cfg.xml file:
<property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.use_second_level_cache">true</property>
Created the file: ehcache.xml and added the below entry in the hbm.xml files:
<cache usage="read-only" />
Below is the code I tried:
SessionFactory sf = new Configuration().configure("trial.cfg.xml").buildSessionFactory();
Session session = sf.openSession();
List departments1 = session.createQuery("from Dept dept where dept.deptId = 1")
.setCacheable(true)
.setCacheRegion("departmentId")
.list();
//Some business operations
session.flush();
session.close();
//Some business operations
Session session1 = sf.openSession();
List departments2 = session1.createQuery("from Dept dept where dept.deptId = 1").list();
//In the above line again it is hitting the DB rather than taking it from the cache.
I believe that I am missing something for which it is not fetching the data from the cache and hence hitting the DB. Kindly let me know how to make this Query Cache work.
The second query is not cacheable, so it doesn't use the cache. As simple as that.
Note that if this is your real code, you should simple use session.get(Dept.class, 1) to get the department.
I have the following code:
Person a = new Person();
a.setName("John");
Session session = openHibernateSession();
session.beginTransaction();
session.saveOrUpdate(a);
Criteria critera = session.createCriteria(Person.class);
critera.add(Restrictions.eq("name","John"));
Person personFromCache = (Person) criteria.uniqueResult();
...
session.commit();
What I want is to have the ability to search objects from both the database and Hibernate's cache. The following example returns null upon calling uniqueResult. Is there any way to retrieve saved objects that have not yet been committed to the database?
If you are searching other than ID then Hibernate will not use first level cache. Hibernate get and load is related to first level cache by default but criteria query is not. In your case there are two solution from my side
By flushing session = Just flush your session like this session.flush(); while doing so data from session will be synchronize to database hence Id will ge generated and as result Criteria query will find the result in database and will result list to you.
Enable hibernate second level cache = You can enable second level cache by hibernate cache providers like ehCache and apply the trick.
You can use the StatelessSession but be warned:
those entitys are not bound to any session and you will get Exceptions if you like to resolve relations or lazy fields!
session.beginTransaction();
session.saveOrUpdate(a);
session.flush();
Criteria critera = session.createCriteria(Person.class);
critera.add(Restrictions.eq("name","John"));
Person personFromCache = (Person) criteria.uniqueResult();
We do some similar things except using TestNg test framework. Several of the answers discuss the session.flush() method call. This is correct. The call to flush tells Hibernate to do several things, including making sure that all database calls currently waiting in the queue get executed and cleared from the queue.
It returns data even if you are selecting on the basis of username. It is not returning null.