I'm using doctrine 2 without caching anything at the moment. I'd like to enable some caching system within Doctrine but it looks like you have to manage it manually everywhere:
$memcache = new Memcache();
$memcache->connect('memcache_host', 11211);
$cacheDriver = new \Doctrine\Common\Cache\MemcacheCache();
$cacheDriver->setMemcache($memcache);
$cacheDriver->save('cache_id', 'my_data');
...
$cacheDriver->delete('cache_id');
I'd like to know if Doctrine could manage this automatically. For instance:
The cache is enable, I request a User entity by id, Doctrine search in its cache, cannot find the user, fetch it, set it into the cache, return it.
I fetch a second time, Doctrine return me the cached User.
I update the User (or any of its relations) Doctrine detect it and break the cache for this object
I request the same User by id, Doctrine doesn't have it in cache anymore, fetch it and set the cache back with the updated user before to return it
Is that possible?
Cheers,
Maxime
What you are looking for (in Doctrine ORM) is only supported in the resultset cache, and only applies to results of SQL queries produced by DQL queries.
The exact name for the feature you are looking for is "second-level cache", which is not yet supported by Doctrine ORM, but is currently being developed (will hopefully be available in version 2.5) at https://github.com/doctrine/doctrine2/pull/580
For now, you will have to handle this kind of caching in your own service layer if it is really needed.
If you are pulling the entity by it's primary key, the caching will be done by the doctrine's "identity map" as described here http://doctrine-orm.readthedocs.org/en/latest/reference/unitofwork.html
Related
When using #Cacheable annotation(org.springframework.cache.annotation.Cacheable) on the repository with custom key like name,getting the issue of running extra query on id field on every consecutive request.
See the repository code below:
public interface StatusRepository extends JpaRepository<Status, Integer> {
#Cacheable(value = "StatusByStatusNameCache" , key="#statusName")
public Status findByStatusName(String statusName);
}
Above you can see that a cache is defined for status name only, now after running first request got the following Hibernate console with query on status name:
Hibernate: select status0_.status_id as status_i1_7_, status0_.status_name as status_n2_7_ from status status0_ where status0_.status_name=?
Hibernate: select event0_.event_id as event_id1_3_, event0_.event_name as event_na2_3_ from events event0_ where event0_.event_name=?
now then hit another second request getting hibernate query in console with id :
Hibernate: select event_.event_id, event_.event_name as event_na2_3_ from events event_ where event_.event_id=?
Hibernate: select requestcha_.request_channel_id, requestcha_.request_channel_name as request_2_6_ from request_channels requestcha_ where requestcha_.request_channel_id=?
Hibernate: select status_.status_id, status_.status_name as status_n2_7_ from status status_ where status_.status_id=?
I don't understand why this extra query is firing as status_name query is cached but how to stop this id query on every consecutive call after first request.
The #Cacheable annotation by Spring is completely independent from the any cache provided by Hibernate/your JPA implementation.
The query by id will not be prevented by caching the result of the by name query, because the caching for the id query would be done by JPAs first level cache, which doesn't know or care about Springs cache.
Here is what is probably going on:
findbyName
entity is in 1st level cache and in Springs cache.
Any access by id (e.g. navigating to the entity)
entity gets served from 1st level cache.
session ends.
entity is removed from 1st level cache
findByName
entity is served from Springs cache. Note that this is now a detached entity. Nothing is in the 1st level cache.
access by id
entity is loaded from database, since it is not found in the 1st level cache.
You should enable Hibernates 2nd level cache to cache entities across sessions for access by id.
I also would advise against combining the caches of JPA/Hibernate with Spring Caches and rather use JPAs own query cache to cache findByName. See Spring JPA Hibernate query cache doesn't work for how to make it work with Spring Data JPA.
Also take a look at this article by Vlad Mihalcea about interaction of query cache and 2nd level cache.
Note that Oliver Drotbohm seems to have a different opinion.
I have in my application list of customers and users. I would like get the list of them only on start. Then use data that is stored locally, not from DB.
Can You advice me some methods with examples?
I think about HttpSession object? But I am not sure is it ok?
Cause this data should be available only for logged user, that access it on start.
List of customers will be available on each page off application!
Take a look at Spring cache http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html
You can annotate your repository methods:
#Cacheable(value="customer", key="#prsonalNum")
public Customer findCustomer(String prsonalNum) {
...
}
The application will enter the method body only the first time. After that the value will be taken from the cache.
You can also evict the cache when you update some customer for example.
#CacheEvict(value="customer", allEntries=true)
public void addCustomer(Customer cust)
For synchronizing insert and update operations with the cache use #CachePut annotation and for synchronizing delete operations with the cache use #CacheEvict annotation.
Use the same cache name (value paramter) and same key value
And you should enable caching with #EnableCaching annotation on one of your configuration classes.
You can still use the HttpSession object, however, only put it in the session once, the user has logged in... and you can remove it from the session on page close...
If you want to reuse the data multiple times and do not want to query each time. i would suggested to create a simple Cache suing HashMap or HashTable.
You can easily save the list of customers across every id in such data structure.
and in spring you can easily create a singleton bean which will hold this hashmap and accessible across the application.
I have a complex entity with many relations, so I need many forms in many pages to create one. Therefore, I use a session to keep my entity.
Everything is going okay, but when comes the time to flush, the entity manager returns the "entity through relationship is not configured to cascade persist" thinking that some entities are new but they're actually stored in db !
For instance, I create a User with a ManyToOne Group, using $u->setGroup(Group $group); ($group being an existing group from the db). When I put it in session, then get it back in another page and then flush it, the entity manager tries to create a whole new group, not knowing that it is an existing one in db.
I use a little trick to overcome this :
$u = $this->get('session')->get('userToAdd');
$group = $em->getRepository('MyBundle\Entity\Group')->find($u->getGroup()->getId());
$u->setGroup($group);
With this, EM will recognize the group stored in db and the flush will go just fine, but with my entity having so much relationships like this, it is very convenient to do this for every single one.
Any ideas for this issue ?
Before find group try to refresh $u object.
$em->refresh($u)
You have to do:
$em->merge($u);
I got the famous LazyInitializationException.
I have an object User which is stored in the session. This object contains an other object Market which is lazy initialized.
When I load the user in the session, I don't load Market because it is too heavy and I don't need it everytime.
When I want to load the market, I am in a different transaction and I don't want to reload the user from the database. How can I retrieve the Market object? Knowing that User.market contains the Hibernate proxy and so the id of the market and that I don't want to hack Hibernate using reflection.
That would be even better if I could load the market without loading it into the user. Since the user is in the session, I don't want to put a lot of stuff in the session.
A JPA compatible solution would be even better.
Cheers
If the eager mode fetching is not acceptable, and if the transaction cannot be maintained up to the Market retrieval, a specific dao method could be implemented to retrieve specifically the market from a user
public List<Market> retrieveMarketFromUser (final User user) {
Query query = session.createQuery("SELECT m FROM User AS u INNER JOIN u.market as m WHERE u.userid = :uid");
query.setParameter("uid", user.getId());
List<Market> list = query.list();
return list;
}
or the short version
Query query = session.createQuery("SELECT u.market FROM User AS u WHERE u.userid = :uid");
This is maybe not the JPA solution you were expecting, just a workaround.
What you have to do is to annotate the accessors instead of the fields. This will allow you to avoid loading of the market object when you initially load the user, but you will have access to the id of the market from the Hibernate proxy object without triggering a lazy loading or getting a LazyInitializationException. Then later on when you want to load the market itself you do a normal entity retrieval based on its id. You can read a detailed explanation of how this works here.
If the relation is bidirectional then you can load the Market independently using a query with clause like where market.user.id = ?.
Why is the obvious solution not good? Like the one ring0 suggested, or simply using the findById or find methods if you already have the id in the User object.
//if using a session factory
session.findById(Market.class, marketId);
//if using the EntityManager
em.find(Market.class, marketId);
Depending on how the current_session_context_class configured (I have it in hibernate.cfg.xml) you might have a new session with each new transaction. If that is the case the you do not need to worry about putting too much stuff in there. You can find more info on contextual sessions here.
Since you have mentioned new transaction , then you would probably working with a new hibernate session belonging to thread ,
Use this incase of direct interaction with hibernate session
obj = session.merge(obj);
In case your using JPA2 Api
obj= entityManager.merge(obj);
Please rate the answer if it helps.
Cheers
When usually fetching an entity from database with doctrine, you get all the related entities as actual classes, which causes a huge JOIN query, if you have lots of relations.
But sometimes I just want to get the actual object, not all the associated entities, just their IDs.
Is it possible to tell doctrine to just fetch the main entity and leave alone the relations?
Update: Sorry, missed the version: I'm using Doctrine 1.2 on a old project.
By default Doctrine use "lazy-loading": it will not retrieve the associated entities if you do not try to access them.
If you just use the ID of the main entity, it will never retrieves the associated entities.
If you want it to be even more lazy, try using the EXTRA_LAZY param.