EclipseLink Cache not getting cached - caching

#NamedQuery(
name=DBConstants.SERVICE_BY_ID, query="select s from Service s where s.id= :id",
hints={
#QueryHint(name = QueryHints.QUERY_RESULTS_CACHE, value = HintValues.TRUE),
#QueryHint(name = QueryHints.QUERY_RESULTS_CACHE_SIZE, value = "500")
})
I am using different entity manager on every query request.
Query query = em.createNamedQuery(DBConstants.SERVICE_BY_ID);
query.setParameter("id", id);
result = (Service) query.getSingleResult();
This is always returning data from database but not from Cache.
How I tested -> I changed some column value in database and when I performed the query, the modified value is coming. Ideally it should return stale data since the cache is not refreshed OR invalidated.
Revision 2:
If we are trying to cache named queries, then using multitenant aware persistence classes will cause problem. How? Here is the way I think it works:
#Cache(
type=CacheType.SOFT,
size=6000,
expiry=600000
)
#Multitenant
#TenantDiscriminatorColumn(...)
#NamedQueries({
#NamedQuery(
name=DBConstants.ALL_SERVICES, query="select s from Service s",
hints={
#QueryHint(name = QueryHints.QUERY_RESULTS_CACHE, value = HintValues.TRUE),
#QueryHint(name = QueryHints.QUERY_RESULTS_CACHE_SIZE, value = "10")
})
})
})
#IdClass(ServicePK.class)
public class Service implements Serializable { ... }
Creation of EMF:
String tenantID = getTenantID();
properties.put(DBConstants.TENANT_ID_CONTEXT_PROPERTY, tenantID);
properties.put(DBConstants.LINK_SESSION_NAME, "session-" + tenantID);
if(emf == null)
emf = Persistence.createEntityManagerFactory("<PERSISTENCE_UNIT_NAME>", properties);
Creation of EM:
em = emf.createEntityManager();
In this case, our named query "ALL_SERVICES" is to search for all services and we want named query cache to cache based on that query. But when we execute this query, EM appends the tenantId to the query i.e.
select s from Service s where s.tenantID = <VALUE>
This is cached in the isolated cache of the L1 cache and is available till you use the same EM instance.
When you create a new EM instance, it checks if the entry is present in the L2 cache before trying to invoke query on DB. L2 caches only based on the query we mentioned but not with the tenantId appended. This causes cache hit miss and the call always goes to backend.
Now... How do I solve this problem? I can remove multitenant annotations and add tenantId myself in the query. I tested it and it works absolutely fine.
But removing multitenant annotation causes lot of condition checks to be done manually especially when we have dependencies on other persistence classes.
I need a better solution. Can anyone help???

Related

Memory leak with Criteria API Pageable

I implemented pageable functionality into Criteria API query and I noticed increased memory usage during query execution. I also used spring-data-jpa method query to return same result, but there memory is cleaned up after every batch is processed. I tried detaching, flushing, clearing objects from EntityManager, but memory use would keep going up, occasionally it will drop but not as much as with method queries. My question is what could cause this memory use if objects are detached and how to deal with it?
Memory usage with Criteria API pageable:
Memory usage with method query:
Code
Since I'm also updating entities retrieved from DB, I use approach where I save ID of last processed entity, so when entity gets updated query doesen't skip next selected page. Below I provide code example that is not from real app I'm working on, but it just recreation of the issue I'm having.
Repository code:
#Override
public Slice<Player> getPlayers(int lastId, Pageable pageable) {
List<Predicate> predicates = new ArrayList<>();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Player> criteriaQuery = criteriaBuilder.createQuery(Player.class);
Root<Player> root = criteriaQuery.from(Player.class);
predicates.add(criteriaBuilder.greaterThan(root.get("id"), lastId));
criteriaQuery.where(criteriaBuilder.and(predicates.toArray(Predicate[]::new)));
criteriaQuery.orderBy(criteriaBuilder.asc(root.get("id")));
var query = entityManager.createQuery(criteriaQuery);
if (pageable.isPaged()) {
int pageSize = pageable.getPageSize();
int offset = pageable.getPageNumber() > 0 ? pageable.getPageNumber() * pageSize : 0;
// Fetch additional element and skip it based on the pageSize to know hasNext value.
query.setMaxResults(pageSize + 1);
query.setFirstResult(offset);
var resultList = query.getResultList();
boolean hasNext = pageable.isPaged() && resultList.size() > pageSize;
return new SliceImpl<>(hasNext ? resultList.subList(0, pageSize) : resultList, pageable, hasNext);
} else {
return new SliceImpl<>(query.getResultList(), pageable, false);
}
}
Iterating through pageables:
#Override
public Slice<Player> getAllPlayersPageable() {
int lastId = 0;
boolean hasNext = false;
Pageable pageable = PageRequest.of(0, 200);
do {
var players = playerCriteriaRepository.getPlayers(lastId, pageable);
if(!players.isEmpty()){
lastId = players.getContent().get(players.getContent().size() - 1).getId();
for(var player : players){
System.out.println(player.getFirstName());
entityManager.detach(player);
}
}
hasNext = players.hasNext();
} while (hasNext);
return null;
}
I think you are running into a query plan cache issue here that is related to the use of the JPA Criteria API and how numeric values are handled. Hibernate will render all numeric values as literals into an intermediary HQL query string which is then compiled. As you can imagine, every "scroll" to the next page will be a new query string so you gradually fill up the query plan cache.
One possible solution is to use a library like Blaze-Persistence which has a custom JPA Criteria API implementation and a Spring Data integration that will avoid these issues and at the same time improve the performance of your queries due to a better pagination implementation.
All your code would stay the same, you just have to include the integration and configure it as documented in the setup section.

NHibernate second level cache: Query cache doesn't work as expected

The packages I use:
NHibernate 5.2.1
NHibernate.Caches.SysCache 5.5.1
The NH cache config:
<configuration>
<configSections>
<section name="syscache" type="NHibernate.Caches.SysCache.SysCacheSectionHandler,NHibernate.Caches.SysCache" />
</configSections>
<syscache>
<!-- 3.600s = 1h; priority 3 == normal cost of expiration -->
<cache region="GeoLocation" expiration="3600" sliding="true" priority="3" />
</syscache>
</configuration>
I want to query a bunch of locations using their unique primary keys. In this unit test I simulate two requests using different sessions but the same session factory:
[TestMethod]
public void UnitTest()
{
var sessionProvider = GetSessionProvider();
using (var session = sessionProvider.GetSession())
{
var locations = session
.QueryOver<GeoLocation>().Where(x => x.LocationId.IsIn(new[] {147643, 39020, 172262}))
.Cacheable()
.CacheRegion("GeoLocation")
.List();
Assert.AreEqual(3, locations.Count);
}
Thread.Sleep(1000);
using (var session = sessionProvider.GetSession())
{
var locations = session
.QueryOver<GeoLocation>().Where(x => x.LocationId.IsIn(new[] { 39020, 172262 }))
.Cacheable()
.CacheRegion("GeoLocation")
.List();
Assert.AreEqual(2, locations.Count);
}
}
If the exact same IDs are queried in the exact same order, the second call would fetch the objects from the cache. In this example however, the query is called with only two of the previously submitted IDs. Although the locations have been cached, the second query will fetch them from the DB.
I expected the cache to work like a table that is queried first. Only the IDs that have not been cached yet, should trigger a DB call. But obviously the whole query seems to be the hash key for the cached objects.
Is there any way to change that behavior?
There is no notion of a partial query cache, it's all or nothing: if the results for this exact query are found - they are used, otherwise the database is queried. This is because the query cache system does not have specific knowledge about the meaning of the queries (eg. it cannot infer the fact that result of a particular query is a subset of some cached result).
In other words the query cache in NHibernate acts as a document storage rather than a relation table storage. The key for the document is a combination of the query's SQL (in case of linq some textual representation of the expression tree), all parameter types, and all parameter values.
To solve your particular case I would suggest to do some performance testing. Depending on the tests and a dataset size there are some possible solutions: filter cached results on a client (something like following), or not use query cache, or you can implement some caching mechanism for the particular query on the application level.
[TestMethod]
public void UnitTest()
{
var sessionProvider = GetSessionProvider();
using (var session = sessionProvider.GetSession())
{
var locations = session
.QueryOver<GeoLocation>()
.Cacheable()
.CacheRegion("GeoLocation")
.List()
.Where(x => new[] {147643, 39020, 172262}.Contains(x.LocationId))
.ToList();
Assert.AreEqual(3, locations.Count);
}
Thread.Sleep(1000);
using (var session = sessionProvider.GetSession())
{
var locations = session
.QueryOver<GeoLocation>().
.Cacheable()
.CacheRegion("GeoLocation")
.List()
.Where(x => new[] {39020, 172262}.Contains(x.LocationId))
.ToList();
Assert.AreEqual(2, locations.Count);
}
}
More information on how the (N)Hibernate query cache works, can be found here.

How can I cache a database query with "IN" operator?

I'm using Spring Boot with Spring Cache. I have a method that, given a list of ids, returns a list of Food that match with those ids:
public List<Food> get(List<Integer> ids) {
return "select * from FOOD where FOOD_ID in ids"; // << pseudo-code
}
I want to cache the results by id. Imagine that I do:
List<Food> foods = get(asList(1, 5, 7));
and then:
List<Food> foods = get(asList(1, 5));
I want to Food with id 1 and Food with id 5 to be retrieved from cache. Is it possible?
I know I can do a method like:
#Cacheable(key = "id")
public Food getById(id){
...
}
and iterate the ids list and call it each time, but in that case I don't take advantage of IN SQL operator, right? Thanks.
The key attribute of Cacheable takes a SpEL expression to calculate the cache key. So you should be able to do something like
#Cacheable(key = "#ids.stream().map(b -> Integer.toString(b)).collect(Collectors.joining(",")))
This would require the ids to always be in the same order
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html#cache-annotations-cacheable-key
A better option would be to create a class to wrap around your ids that would be able to generate the cache key for you, or some kind of utility class function.
Another possible Solution without #Cacheable would be to inject the cache manager into the class like:
#Autowired
private CacheManager cacheManager;
You can then retrieve the food cache from the cache manager by name
Cache cache = cacheManager.getCache('cache name');
then you could adjust your method to take in the list of ids and manually add and get the values from cache
cache.get(id);
cache.put(id, food);
You will most likely still not be able to use the SQL IN clause, but you are at least handling the iteration inside the method and not everywhere this method is called, and leveraging the cache whenever possible.
public List<Food> get(List<Integer> ids) {
List<Food> result = new ArrayList<>();
for(Integer id : ids) {
// Attempt to fetch from cache
Food food = cache.get(id);
if (food == null) {
// Fetch from DB
cache.put(id, food);
}
result.add(food);
}
return result;
}
Relevant Javadocs:
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/cache/CacheManager.html
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/cache/Cache.html

Fetch history of records using LINQ

I am using entity framework with repository pattern and unit of work objects..
I have an entity Request with properties "RequestId", "OldRequestId", which can be accessed using requestRepository object.
eg: requestRepostiory.GetAll(), requestRepository.GetFiltered(r=> r.Requestid =10)
If I pass a RequestId, it should retrieve me the specific record.
If the OldRequestId is not null in the retrieved record, it should bring the old request data as well.
It should go on until the OldRequestId is null.
Simple way would be something like this:
public static IEnumerable<Data> GetRecursive(int id)
{
while (true)
{
var tmp = GetFiltered(x => x.Requestid == id);
yield return tmp;
if (tmp.OldRequestId.HasValue)
id = tmp.OldRequestId.Value;
else
yield break;
}
}
Please note, that this code would run make multiple queries towards the database. Performance won't be the best, but it might work for your scenario.

spring-data: cache a queries total count

I'm using spring data jpa with querydsl. I have a method that returns query results in pages containing total count. getting the total count is expensive and I would like to cache it. how is that possible?
My naive approach
#Cacheable("queryCount")
private long getCount(JPAQuery query){
return query.count();
}
does not work (to make it work they way wanted the actually key for the cache should not be the whole query, just the criteria). Anyway tested it, did not work and then I found this: Spring 3.1 #Cacheable - method still executed
The way I understand this I can only cache the public interface methods. However in said method I would need to cache a property of the return value, eg.
Page<T> findByComplexProperty(...)
I would need to cache
page.getTotalElements();
Annotating the whole method works (it is cached) but not the way I would like. Assume getting total count takes 30 seconds. Hence for every new page request user needs to wait 30 sec. if he goes back a page, then the cache is used but I would want the count to be only run exactly once and then count is fetched from cache.
How can I do that?
My solution was to autowire the cache manager in the class creating the complex query:
#Autowired
private CacheManager cacheManager;
and then create a simple private method getCount
private long getCount(JPAQuery query) {
Predicate whereClause = query.getMetadata().getWhere();
String key = whereClause.toString();
Cache cache = this.cacheManager.getCache(QUERY_CACHE);
Cache.ValueWrapper value = cache.get(key);
if (value == null) {
Long result = query.count();
cache.put(key, result);
return result;
}
return (Long)value.get();
}

Resources