I'm having trouble with #Cacheable using with multi parameters and pagination.
#Cacheable(value = "books", key = "#p0")
public List<Book> findBooks(Long loggedUserId, String q, Integer firstResult, Integer maxResults) {
...
return result;
}
The problem is when I call the method by the first time, the content is cached with success and the result is like I expect. But when I call by the second time, the result returned is equals to the first time. If I disable the cache or remove the key the results are different.
The use of the key is obligatory because sometimes the cache of a specified user is removed.
Thanks.
I think your issue is a lack of understanding of the Cacheable annotation, and you you are using it. So let me see if I can help
Your annotation is #Cacheable(value = "books", key = "#p0")
This means that when this method is called it will take the first parameter and look it up in the cache and if there is a result, it will be returned instead of executing the method. IT IS ONLY CHECKING THE FIRST PARAMETER.
Your cache key needs to be something that uniquely identifies the result set. In this case user id is common across multiple results, and does not uniquely identify a page.
You know your use case better than I, but something like this would probably be better:
#Cacheable(value = "books", key = {"#p1","#p2","#p3"})
public List<Book> findBooks(Long loggedUserId, String q, Integer firstResult, Integer maxResults) {
...
return result;
}
The above will cache based on the search query and the index of the page (start and end of results). Since I don't know your use case I don't know why user id is in there, but from the information I have those three should uniquely identify a result page.
Related
I'm experiencing what seems to be a record lock between sequential (not concurrent) database operations, which I can't explain.
Situation
Method saveRegistrationToken is called from a REST controller. I test the calls to the method via Postman (HTTP client); the method is not called anywhere else, this is the only operation executed.
The method execution is supposed to behave as follows:
Take a String (registration token) and a user ID (also a string) as input
It should first update a USERS table, setting the value of the REGISTRATION_TOKEN column to null for every row where the column has the same value as the input registration token
It should then update the USERS table for the row with the specified user ID, setting the value of the REGISTRATION_TOKEN column to the input registration token.
Problem
Every first execution of the method will behave as expected: sets the value of the DB column REGISTRATION_TOKEN (table USER) to null wherever it is the specified value, and then sets the registration token to the input value for the row with the input user ID. As such, the value of the registration token for the row in question is the input value at the end of the execution of the method.
Every second execution will correctly do the first step ("void" the registration token wherever it exists) but does not update the value for the row with the specified user ID. As such, the value of the registration token for the row in question is null at the end of the execution of the method.
DefaultUserService.java
#Override
public void saveRegistrationToken(String userId, String registrationToken) {
usersRepository.voidRegistrationToken(registrationToken);
User u = usersRepository.findById(userId).get();
u.setRegistrationToken(registrationToken);
usersRepository.save(u);
}
UsersRepository.java
#Repository
public interface UsersRepository extends JpaRepository<User, String> {
#Modifying
#Transactional
#Query(value = "UPDATE USERS " +
"SET registration_token = null " +
"WHERE registration_token = :registration_token",
nativeQuery = true)
void voidRegistrationToken(#Param("registration_token") String registrationToken);
}
User.java
#Entity(name = "users")
#AllArgsConstructor //lombok
#Data
#NoArgsConstructor
#ToString
#EqualsAndHashCode
public class User {
#Id
private String id;
private String registrationToken;
private String email;
}
What I've tried
I initially thought it would be a flush problem: that once the registration token had been set to null everywhere, the transaction would not be flushed until after the registration token had been set again for the user ID, leading to conflicting behaviour between both DB operations. I disproved that explicitly calling usersRepository.flush(); after the first operation, and observing the same behaviour.
I tried different propagation and isolation levels on the repository operation: #Transactional(propagation = Propagation.SUPPORTS, isolation = Isolation.READ_UNCOMMITTED), which didn't help.
I tried explicitly setting the flush mode on the repository operation: #QueryHints(value = { #QueryHint(name = org.hibernate.annotations.QueryHints.FLUSH_MODE, value = "ALWAYS") }) , which didn't change anything.
It now seems to me that the first operation "locks" the updated record, which prevents the second operation from updating it, but I don't understand how.
Explicitly specifying auto-commit true: spring.datasource.auto-commit=true
Dependencies: compile("org.springframework.boot:spring-boot-starter-data-jpa") effectively version 2.1.1.RELEASE
Any ideas, explanations, links to docs would be very much appreciated - I've tried everything I can think of.
Many thanks, Chris
UPDATE:
Another reason I think it's some kind of flush problem.
I updated this method as follows:
#Override
public void saveRegistrationToken(String userId, String registrationToken) {
usersRepository.voidRegistrationToken(registrationToken);
String check = usersRepository.findById(userId).get().getRegistrationToken();
/* breakpoint on the following line */
User u = usersRepository.findById(userId).get();
u.setRegistrationToken(registrationToken);
usersRepository.save(u);
}
When stopping at the breakpoint where indicated:
Every first ("normal") execution, the value of the check variable is null
Every second execution, its value is the same as the input registration token
Although I always prefer to mark the service method as #Transactional as a whole, reviewing your code, I think you defined the appropriate transaction demarcation in your methods, by explicitly define the #Transactional annotation in voidRegistrationToken, and by using the methods provided by JpaRepository, implicitly annotated in such a way.
In any case, as you indicated, as a result of performing the different operations over the User who will be assigned the registration token, you are obtaining inconsistent values.
It is a clear indication that the information of the affected User entity maintained by the EntityManager in the persistence context is being polluted somewhere across the different methods invocation.
I honestly cannot give you the exact reason about this behavior.
It may have to do with the moment in which the changes are flushed to the database, to entirely discretion of the EntityManager, but you already tried to flush the different changes manually and your transactions seems appropriate and, as a consequence, it will probably not be the cause of the problem.
Maybe it have to do with a second level cache as #Guillaume suggested, maybe with the way the #Modifying operation is implemented by Spring Data.
One think you can try is to instruct your #Modifying annotation to clear the persistence context once the operation is completed:
#Modifying(clearAutomatically = true)
This will provide you a clean state to perform the registration token update.
Please, see the relevant docs.
Please, be aware that the possible implications of the use of this solution.
The use of flush and clear in EntityManager is an anti-pattern, something that should be avoided if you can by using the appropriate transaction demarcations and component architecture.
The call of clear leads to all objects being decoupled from the EntityManager. Be aware that, depending of the use case, even modified object data will not be saved to the database - this is the main difference with flush that always will preserve the modifications performed to the entities managed in the persistence context before detach them.
In any case, due to the way your transactions are defined, probably in your use case it will work properly.
How is the #Cacheable annotation notified about the changes in the database. For eg : If I Cache the result of CRUDRepository.findAll() function call by annotating the function call by #Cacheable the result is stored in a named cache. The Cache however continues to be updated with DB changes. Consider the below example :
#Cacheable(value = "employee", key = "#id")
public Iterable<Employee> getAllEmployees(){
long startTime = System.currentTimeMillis();
Iterable<Employee> itr = employeeEntityRepository.findAll();
long stopTime = System.currentTimeMillis();
System.out.println("Time-->" + (stopTime - startTime));
return itr;
}
When this method is called the first time it takes 300 ms and the 2nd time it takes hardly 5 ms which means it Caches the result set. Fair enough. But now if I update a record in the employee table directly in the DB and call this method again two things happen :
The new record is returned by this method call.
The method returns very fast.
This means that the cache is updated from the DB every time. How does this Sync work if the resultset is returned from Cache ?
thanks.
The main question is: cache is updated from the DB every time. How does this Sync work if the resultset is returned from Cache?
As per spring doc (5.3.7) #Cacheable annotation is used when we need to cache the result of a call.
in the above scenario caching behavior will be applied, it's checked if the method was already invoked for the given arguments.
below attributes are used in your example. Their description is as below:
value : this name of cache, in above case its "employee"
Key : this is SpEl, Spring Expression Language, to compute the key dynamically.
Ideally, you should have "id" as method-argument in the method getAllEmployees().
But this method seems to return all employees, so it seems incorrect usage in this scenario.
SpEl expression is "#id" = a custom key generator
By default it is "" = all method parameter are considered as key.
If no value is found in the cache for the computed key, the target method will be invoked and returned value will be stored in the associated cache.
The scenario which I encountered is that the method which is annotated by #Cacheable
#Cacheable(value= myConstant,key="#accountId")
public Integer getAccountNumber( Integer accountId){.
// an API call is made here..}
So, cache is not updated from DB, it's updated from a call (API call) next, the result is stored in the cache when the method is invoked for the first time. When the method is called a second time the cached result is returned and API call in the method do not happen.
I have referred: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/annotation/Cacheable.html
I am following the code for the running SQL queries in the Ignite cache, but am able to fully realize the use of the CacheConfiguration.setIndexedTypes API.
I am following the only help that I could find at the ignite site.
The documentation here says to use
CacheConfiguration.setIndexedTypes(MyKey.class, MyValue.class).
Now lets say in the Person class
#QuerySqlField(index = true)
private long id;
#QuerySqlField
private String firstName;
Which are the parameters that I should be passing in the setIndexedType method?
setIndexedTypes takes an even number of parameters. Every odd parameter corresponds to a key type, and every even - to a value type. In your case you should probably use id parameter as a key, so you should call it this way:
cacheConfig.setIndexedTypes(Long.class, Person.class);
Javadoc for setIndexedTypes method contains a pretty good explanation of this method: https://ignite.apache.org/releases/latest/javadoc/org/apache/ignite/configuration/CacheConfiguration.html#setIndexedTypes(java.lang.Class...)
UPD:
There will be registered a table in SQL for each pair of parameters that you provide to setIndexedTypes method.
Your SQL entities will map to cache records and they will have _key and _val columns in addition to the ones that you configured as QuerySqlField-s. So, you should specify types of keys and values that will be used in cache for each table.
You can refer to this page for more information: https://apacheignite.readme.io/docs/dml#basic-configuration
In your case it will be
cacheConfig.setIndexedTypes(KeyType.class, Person.class)
where KeyType is the type you use for keys while calling cache.put(key, person) or insert into Person(_key, ...) ...
Please refer to this documentation section
I'm using a :
org.springframework.data.mongodb.repository.MongoRepository
I start with an empty DB and create an object with _id = 1234 for example, and set some other String field to hello for example, and then do:
repository.save(object);
All is well, it saves the document in MondoDB.
I create a NEW object, set the same _id = 1234 but set the other String field to world and then to another save :
repository.save(newObject);
Results : the save works but updates the original object.
Expected results: This should fail with a DuplicateKeyException as _id is unique and I am using 2 separate objects when doing each save.
Defect in spring or am I doing something wrong ???
Save, by definition, is supposed to update an object in the upsert style, update if present and insert if not.
Read the save operation documentation on the MongoDb website
The insert operation in mongodb has the behavior you expect, but from the MongoRepository documentation it appears that insert is delegated to save so it won't make any difference. But you can give that a try and see if it works for you. Otherwise you can just do a get before to check if the object exists, since it is an index lookup it will be fast.
Edit: Check your repository version, insert was introduced in version 1.7.
the application shall update only when you have #Id annotation for one of the field, after long difficulty had found this
#Document(collection="bus")
public class Bus {
// #Indexed(unique=true, direction=IndexDirection.DESCENDING, dropDups=true)
#Id
private String busTitle;
private int totalNoOfSeats;
private int noOfSeatsAvailable;
private String busType;
}
but somehow I could not use
#Indexed(unique=true, direction=IndexDirection.DESCENDING, dropDups=true)
I am using Spring security for my web services. I'm using annotations to specify the security conditions. Right now I'm facing the following problem. Consider the following service method:
#Override
#PostFilter("hasPermission(filterObject, 'read')")
#Transactional(readOnly = true)
public List<UserDTO> listUsers(int firstResult, int maxResults, String orderBy, boolean ascending) {
//Retrieve users and put them in a collection
}
Post filtering works after the method is invoked, but the contract of the method is to return a collection of size #maxResults if at least that many users exist. How do I achieve this, because objects may be filtered out after the method has been invoked. Of course I could revert to manual access checking inside the method, but I thought that there maybe would be a more elegant way of doing this.