I have two methods to fetch an entity with two different parameters. I also have a save method that uses one of those parameters. How can I evict the entity from the cache under both fetch keys? e.g. see below:
#Cacheable
public User getByUsername(String username);
#Cacheable
public User getByEmail(String email);
#CacheEvict(key="#entity.username")
User save(User entity);
In the above, a call to getByEmail will return stale date.
There are several options, of course, but as usual, Spring has your back.
The easiest and most simple approach is to leverage Spring's #Caching annotation on your save method, like so...
#Caching(evict = {
#CacheEvict(cacheNames = "Users", key="#user.name"),
#CacheEvict(cacheNames = "Users", key="#user.email")
})
User save(User user);
For your reference, I created an example test class demonstrating this working here.
You will notice I imitated your example above using Spring's Cache Abstraction annotations on my UserRepository. In this case, my repo is backed by Pivotal GemFire, but any data store will work. I use a ConcurrentMap as my caching provider, using Spring's ConcurrentMapCacheManager, but of course, any caching provider will do.
My test case proceeds to save a new User, ensuring that the user is stored by not yet cached. The test then proceeds to exercise the query methods (findByName, findByEmail) ensuring that the user entity is cached appropriately in each case. I then remove the entity from the underlying data store and ensure that the entity is still cached. And finally, the moment of truth, I modify the entity, re-save the entity, asserting that the entity is stored by that all entries have been "evicted" from the cache.
You could also try, as another optimization, to combine the #CachePut annotation with the 2 #CacheEvict annotations in this case, which could restore the cache based on the new, updated entity, something like...
#Caching(
evict = {
#CacheEvict(cacheNames = "Users", key="#a0.name", beforeInvocation = true),
#CacheEvict(cacheNames = "Users", key="#a0.email", beforeInvocation = true)
},
put = {
#CachePut(cacheNames = "Users", key="#result.name"),
#CachePut(cacheNames = "Users", key="#result.email")
}
)
User save(User user);
NOTE: notice the user of the beforeInvocation attribute on the #CacheEvict annotations along with the #CachePuts
However, you may prefer that the entity be lazily added to the cache based on need.
Although, you would presume the entity is being frequently accessed/used since the save method on your repo was just called, and therefore rely on your underlying data store (such as GemFire) to set additional eviction (based on overflow)/expiration (based on LRU) settings, thereby better managing your system resources (e.g. memory) while still maintaining optimal application performance.
Food for thought.
Hope this helps.
Related
In our Angular + Spring boot application application, we have 2 Controllers (2 Services are internally referenced). In first controller, We are sending a File from UI and reading the content of the file , query an external application and retrieve a set of data and return only a sub-set of Data, for entering as recommendation for UI fields. why we are returning only sub-set of data received from the external application? Because, we need only those sub-set data for showing recommendations in UI.
Once the rest of the fields are filled, then, we call another controller to generate a report. But, for generation of files, the second service requires the rest of the data from external application, which is received by the first service. I understand that Autowiring the first service in the second service, will create new instance of the first service and I will not get the first service instance, which is used to query the external application. I also like to avoid calling the external application again to retrieve the same data again in the second service. My question is how to fetch the data received by the first service in the second service?
For example:
First controller (ExternalApplicationController), which delegates loading of loading/importing of data from files
public class Department{
private Metadata metadata; // contains data such as name, id, location, etc.,
private Collection<Employee> employees; // the list of employees working in the department.
}
#RestController
#RequestMapping("/externalApp")
public class ExternalApplicationController{
#Autowired
private ExternalApplicationImportService importService;
#PostMapping("/importDepartmentDataFromFiles")
public Metadata importDepartmentDataFromFiles(#RequestParam("files") final MultipartFile[] files) {
return this.importService.loadDepartmentDetails(FileUtils.getInstance().convertToFiles(files)).getMetadata();
}
}
The first service (ExternalApplicationImportService), which delegates the request to the external application for loading of department data.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
public Department loadDepartmentDetails(File file){
return app.loadDepartmentDetails(file);
}
}
The Metadata from the ExternalApplicationController is used to populated UI fields and after doing some operations (filling up some data), user requests to generate a report(which contains details from the employees of that department)
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#Autowired
private ReportGenerationService generationService;
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(){
generationService.generateAnnualReports();
}
}
#Service
public class ReportGenerationService{
public void generateAnnualReports(){
//here I need access to the data loaded in the ExternalApplicationImportService.
}
}
So, I would like to access the data loaded in the ExternalApplicationImportService in the ReportGenerationService.
I also see that there would be more services created in the future and might need to access the data loaded in the ExternalApplicationImportService.
How can this be designed and achieved?
I feel that I'm missing something how to have a linking between these services, for a given user session.
Thanks,
Paul
You speak about user session. Maybe you could inject the session of your user directly in your controllers and "play" with it?
Just adding HttpSession as parameter of your controllers' methods and spring will inject it for you. Then you just have to put your data in the session during the first WS call. And recover it from the session at the second WS call.
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(HttpSession session){
generationService.generateAnnualReports();
}
}
Alternatively for the second call you could use:
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(#SessionAttribute("<name of your session attribute>") Object yourdata){
generationService.generateAnnualReports();
}
}
You are starting from a wrong assumption:
I understand that Autowiring the first service in the second service, will create new instance of the first service and I will not get the first service instance, which is used to query the external application.
That is not correct: by default, Spring will create your bean as singleton, a single bean definition to a single object instance for each Spring IoC container.
As a consequence, every bean in which you inject ExternalApplicationImportService will receive the same instance.
To solve your problem, you only need a place in where temporarily store the results of your external app calls.
You have several options for that:
As you are receiving the same bean, you can preserve same state in instance fields of ExternalApplicationImportService.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
// Maintain state in instance fields
private Department deparment;
public Department loadDepartmentDetails(File file){
if (department == null) {
department = app.loadDepartmentDetails(file);
}
return department;
}
}
Better, you can use some cache mechanism, the Spring builtin is excellent, and return the cached result. You can choose the information that will be used as the key of the cached data, probably some attribute related to your user in this case.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
#Cacheable("department")
public Department loadDepartmentDetails(File file){
// will only be invoked if the file argument changes
return app.loadDepartmentDetails(file);
}
}
You can store the information returned from the external app in an intermediate information system like Redis, if available, or even in the application underlying database.
As suggested by Mohicane, in the Web tier, you can use the http sessions to store the attributes you need to, directly as a result of the operations performed by your controllers, or even try using Spring session scoped beans. For example:
#RestController
#RequestMapping("/externalApp")
public class ExternalApplicationController{
#Autowired
private ExternalApplicationImportService importService;
#PostMapping("/importDepartmentDataFromFiles")
public Metadata importDepartmentDataFromFiles(#RequestParam("files") final MultipartFile[] files, HttpSession session) {
Deparment department = this.importService.loadDepartmentDetails(FileUtils.getInstance().convertToFiles(files));
session.setAttribute("department", department);
return deparment.getMetadata();
}
}
And:
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#Autowired
private ReportGenerationService generationService;
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(HttpSession session){
Department department = (Department)session.setAttribute("department");
// Probably you need pass that information to you service
// TODO Handle the case in which the information is not present in the session
generationService.generateAnnualReports(department);
}
}
In my opinion, the second of the proposed approaches is the best one but all are valid mechanisms to share your data between the two operations.
my recommendation for you will be to revisit your design of classes and build a proper relationship between them. I feel you need to introduce the extra logic to manage your temporal data for report generation.
#Mohicane suggested to use HTTP Session in above answer. It might be a possible solution, but it has an issue if your service needs to be distributed in the future (e.g. more than one runnable instance will serve your WEB app).
I strongly advise:
creating a separate service to manage Metadata loading process, where you will have load(key) method
you need to determine by yourself what is going to be a key
both of your other services will utilize it
this service with method load(key) can be marked by #Cacheable annotation
configure your cache implementation. As a simple one you can use In-Memory, if a question becomes to scale your back-end app, you can easily switch it to Redis/DynamoDB or other data storages.
Referances:
Spring Caching
Spring Caching Guide
I can't understand why cache evict is not working in my scenario. I have an application that has a scheduled service in it and has MVC for user to click some stuff.
#Cacheable(value = "applicationToken")
public Optional<String> getToken() {
return settingsRepository.getToken();
}
#CacheEvict(value = "applicationToken", allEntries = true)
public void evictApplicationTokenCache() {
log.info("Evicting token cache.");
}
public void updateToken(String token) {
log.info("Updating token.");
settingsRepository.updateToken(token);
evictApplicationTokenCache();
}
The method getToken() is called inside the scheduled service and when I tried some test to evict cache from there it worked.
However, on the MVC side, if the user updates the token, the method updateToken() gets called and although it goes inside the evictApplicationTokenCache(), on the next retrieval of the token, I still get the same token and it doesn't step into the method getToken() to actually grab the token from the repository.
The only relation I found is that the threads are different for the MVN call and for the Scheduled call. From what I know, the cache should live on the context level, not the thread level. Therefore, it shouldn't matter which thread asks for the cache to be evicted.
It seems that the updateToken() and evictApplicationTokenCache() methods are in the same class.
In that case, the #CacheEvict annotation will be ignored, because cache handling is implemented by interceptors that are only involved when you call a method from one component to a different (injected) component.
If that's the situation, you can move the evictApplicationTokenCache() method to a helper #Component, or put the #CacheEvict annotation on the updateToken() method.
I have a impl class where I have 2 update methods , method1 is updating complete row in DB whereas method2 is updating only one column in DB of that table.
Now I need to use #Caceable and #CacheEvict here , How can I use in this condition ?
From Spring 3.1 introduces a new feature to allow methods to be cached and evicted thus allowing resource heavy methods to be avoided where possible. Caching is enabled via the new #Cacheable and #CacheEvict annotations.
One example of caching would be, for example, database activity. We can apply the #Cacheable annotation to a find operation and then apply the #CacheEvict to an update / delete operation. In this sense, caching would work much like a second level cache in Hibernate or JPA.
To enable caching on a find method, the method needs to be annotated with the #Cacheable annotation identifying which cache to use. Spring allows multiple caches to be defined each of which can be backed by a different caching abstraction.
#Cacheable("items") //#Cacheable(value = "items", key = "#itemId")
public Item find(long itemId) {
Item item = entityManager.find(Item.class, itemId);
return item;
}
When it is time to invoke the find method, Spring checks in the specified cache to see if the results of the operation have already been cached and if the results can be therefore be returned from cache instead of invoking the method. Spring uses the method arguments as the key, so in this case the itemId parameter.
To evict an entry from the cache when an object is updated in the database, the #CacheEvict annotation can be used. Again, this annotation takes a parameter identifying which cache to use.
#CacheEvict(value = "items", key = "#item.id")
public void updateItem(Item item) {
entityManager.merge(item);
}
EDIT:
#CacheEvict
Used for Cache-removal /cache-cleanup operation. #CacheEvict annotation indicates that a method (or all methods on a class) triggers a cache evict operation, removing specific [or all] items from cache. Various attributes provides complete control to enforce the required behavior for cache-eviction.
for example,
#CacheEvict(value = "products", key = "#product.name")
public void refreshProduct(Product product) {
//This method will remove only this specific product from 'products' cache.
}
#CacheEvict(value = "products", allEntries = true)
public void refreshAllProducts() {
//This method will remove all 'products' from cache, say as a result of flush-all API.
}
Accessing the DB repeatedly for individual entities is much slower than doing a bulk select. How do I cache a the result of a bulk select into a cache, and later access it individually?
For example, I have a Employee entity:
public class Employee {
private Integer id;
}
And I have repository that can access it either with bulk select, or individually by id:
public class EmployeeRepository {
public Map<Integer, Employee> retrieveByEmployeeIds(List<Integer> ids) {
// impl
}
public Employee retrieveByEmployeeId(Integer id) {
// impl
}
}
How do I implement it so that when retrieveByEmployeeId(Integer id) is called it will check the same cache as retrieveByEmployeeIds(List<Integer> ids), and if it doesn't exist it'll make a call to the DB, and also storing that cache with the id again?
I have answered similar questions before, for example see (Spring Cache with collection of items/entities).
Essentially, you must implement a custom CacheManager and Cache, the 2 primary interfaces that form the basis of Spring's Cache Abstraction, as described here. It may even extend or delegate to an existing caching provider, but you must "decorate" the existing functionality.
The link I referred to above also contains examples.
Hope this helps give you ideas on how to handle your particular UC.
A Grails 2.3.4 application is connecting to an Oracle database using the following domain class:
class Person {
String name
static mapping = {
id column: "PERSON_ID", generator: "sequence", params: [sequence: 'person_seq']
}
}
The PersonController makes a call to a method in PersonService and it makes a call to UtilService. The method in UtilService being called has some logic based on wether this Person object is new:
if (personInstance.id == null) { ... }
What I have found is that the id property of personInstance (which is passed through the method calls described above) is assigned when the UtilService is called.
The controller action calling PersonService is #Transactional, and the services do not have any transaction configuration.
So, a couple of questions:
When is id value assigned by GORM (I assumed at insert but that seems wrong)?
Is there a better way of checking if the object is new (isAttached() returns true so that's not good for me)?
EDIT: save() has not been called on the personInstance when UtilService does the id check.
The id is assigned when you call save(). For most persistence calls, Hibernate delays flushing the change until it feels it has to flush) to ensure correctness. But save() calls are treated differently. I believe the motivation is that even if we know that flush() will eventually be called, even if it's at the very end of the request, we want to retrieve the id early so it doesn't suddenly change.
Note that a service that does "not have any transaction configuration" is transactional - the only way to get a non-transactional service is to remove all #Transactional annotations (the newer Grails annotation and the older Spring annotation) and add
static transactional = false
All other services are transactional, although you can configure individual methods to be ignored ## Headin.
Turns out, I had a findBy which was flushing the session:
utilService.someMethod(Person.findByUsername(username))
It was at this point that the id was populated.
Got around it by using withNewTransaction:
def personInstance = Person.withNewSession { Person.findByUsername(username) }
utilService.someMethod(personInstance)
Which now leads me onto the next question...