I am building a RESTful web service that can be consumed by a browser or another web service.
I am willing to reduce the bandwidth through caching, however i want the method to be executed and send the actual data only if it's different than the last modified cache.
From my understanding of the #cacheable annotation, the method is only executed once and the output is cached until the cache expires .
Also #CachePut executes everytime and updates the cache but does it send the cache again even if it's not updated?
summary is: i need the client to be able to send the last modified date of it's cache and only get a new data if it has been modified.
Also how does Spring handle the client side caching and if-modified-since headers? does i need to save the last modified time or it is automatically handled ?
No, you need to do it by yourself.
You need to annotate your "fetch" method with #Cacheable(docs) and then, annotate "update" method with #CacheEvict (docs) in order to "drop" your cache. So when you would fetch your data next time after its modification, it will be fresh.
Alternatively, you can create another method with #CacheEvict and manually call it from "update" method.
The cache related annotations (#Cacheable, #CacheEvict etc) will only deal with the cache being maintained by application. Any http response header like last-modified etc has to be managed seperately. Spring MVC provides a handy way to deal with it (docs).
The logic to calculate the last modified time has to be obviously application specific.
An example of its usage would be
MyController {
#Autowire
CacheService cacheService;
#RequestMapping(value = "/testCache", method = RequestMethod.GET)
public String myControllerMethod(WebRequest webRequest, Model model, HttpServletResponse response) {
long lastModified = // calculate as per your logic and add headers to response
if (request.checkNotModified(lastModified)) {
// stop processing
return null;
} else {
return cacheService.getData(model);
}
}
#Component
public class CacheService{
#Cacheable(value = "users", key = "#id")
public String getData(Model model) {
//populate Model
return "dataview";
}
Related
I am working on a fallback procedure for when the connection fail (or another error) occurs. I've created the CacheConfiguration/CacheErrorHandler to handle the errors and log them. The application successfully switches between using the cache and going through the normal process when Redis fails.
However, the way I've implemented cache eviction endpoint (via the #cacheEvict annotation), it is essentially an empty method.
#DeleteMapping(value = "/cache/clear")
#CacheEvict(value = {_values_}, allEntries = true)
public ResponseEntity<String> clearAllCache() {return ResponseEntity.ok("OK"); }
Current CacheErrorHandler
#Override
public CacheErrorHandler errorHandler() {
return new CacheErrorHandler() {
#Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
LOGGER.warn("Failure evicting from cache: " + cache.getName() + ", exception: " + exception);
}
}
Logger will output the cacheEvictError but the response will send back "OK" to the client.
Is there a way to catch the cache error and send a different response saying that the cache evict failed?
I've tried adding a try-catch to throw an exception inside the endpoint but that went nowhere. Couldn't seem to find any examples online to solve this specific issue.
One thing to keep in mind here is that Spring's #CacheEvict annotation and behavior is called "after" the method (by default) on which the annotation is declared, which in your case is the clearAllCache() Web service method.
Although, you can configure the cache eviction to occur before the (actual) clearAllCache() Web service method is called, like so:
#CacheEvict(cacheNames = { ... }, allEntries = true, beforeInvocation = true)
public ResponseEntity<String> clearAllCache() {
// ...
}
That is, using the beforeInvocation attribute on the #CacheEvict annotation, set to true, the cache eviction (for all entries) will occur before the actual clearAllCache() method is invoked.
NOTE: Logically, if the invocation happens after the clearAllCache() method has already been called, then you really have no way to respond if the cache eviction (or rather, the "clear" operation) was unsuccessful. So you must configure the cache eviction to occur before your Web service method gets invoked, first of all.
Next, you need someway to know that your custom CacheErrorHandler was invoked on an error occurring in your caching provider (e.g. Redis) during eviction (or technically, the Cache.clear() operation in this case, since you evicting "all entries").
Another thing to keep in mind here is that since you appear to be operating in Web environment (e.g. a Servlet container like Tomcat or Jetty, or other) then you need to keep "Thread Safety" in mind since each HTTP request and corresponding Web handler method, like the clearAllCache() method called on HTTP DELETE, will be invoked from a separate Thread (i.e. Thread per (HTTP) Request model).
So, you can solve that problem using a Java ThreadLocal declared inside your custom CacheErrorHandler class to capture the necessary state / information that is needed once the clearAllCache() method is called.
I have wrote one such example test class demonstrating how you could accomplish this. The key to this implementation (solution) is the proper configuration of the cache eviction and the use of the ThreadLocal in the custom CacheErrorHandler.
My test is not specifically configured as a Web-based service (e.g. using Spring Web MVC, or anything like that), but I modeled the test use case after your particular situation. I also made use of Mockito to spy on the Spring caching infrastructure to always throw a RuntimeException anytime a Cache eviction based operation occurs (e.g. evict(key) or clear(), etc).
Of course, there are probably better, more robust ways to implement this solution, but this at least demonstrates that it is possible.
Hopefully, this gives you more ideas.
I'm writing web app with spring security, I have already default security implementation it works fine, but I have question about getting data from database. How can I keep information from database without executing query to database everytime?
Look at this. User is my entity class, but for me it is not effective to retrive data from database everytime. Everytime I refresh that /welcome it will execute query, I'm using Spring data jpa so it's fine but does not make sense since there is no change in database. So what I want to do is to keep user and retrive his data from db once in my app. Is there any way to do it?
#RequestMapping(value = "/userpanel")
public String userpanel(Model model, Principal principal){
String loggedUserName = principal.getName();
Optional<User> user = userService.findByUserName(loggedUserName);
if(user.isPresent()){
model.addAttribute("user", user.get());
}
return "userpanel";
}
That's exactly what a custom UserDetails object is for. Make your User entity implement UserDetails, and make your UserDetailsService return it.
This way, SecurityContextHolder.getContext().getAuthentication().getPrincipal() will return your entity. You will also be able to inject it using #AuthenticationPrincipal.
This of course assumes that you have not set session creation to stateless (i.e. your security provider is not re-authenticating the user for every request).
Just remember that it is bad practice to keep sensitive data in memory for a prolonged period of time.
Spring includes its own caching system: https://spring.io/guides/gs/caching/
To use it, annotate a method that may take while to fetch something from the database with #Cacheable(String)
Then, in the application class, you can add the #EnableCaching annotation.
Google Guava also has its own caching system.
Example usage is like so:
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<String, User>() {
public Graph load(User user) throws AnyException {
return loadFromDatabase(user);
}
});
This will store a maximum of 1000 users in memory and deletes entries when it gets too big. It will also delete entries after 10 minutes.
It acts like a map, but calls the load(User) method if the key (user ID) is not present.
I am trying to implement #async with #cacheable as below ..
#RequestMapping(value="ehcacheExample")
public ModelAndView loadehcacheExamplePage(HttpServletRequest request, HttpSession session, ModelAndView modelAndView) {
cacheHelper.getDataUsingAsyncAndStoreInEhcache(userInfo, session);
//diffetent execution
...
return modelAndView;
}
And
#Cacheable(value="cacheKey")
public Future<String> getDataUsingAsyncAndStoreInEhcache(UserLoginSessionInfo userInfo,
HttpSession session) {
return ehcacheExampleDelegate.getDataUsingAsyncAndStoreInEhcache(userInfo,session);
}
#Async
public Future<String> getDataUsingAsyncAndStoreInEhcache(UserLoginSessionInfo userInfo,
HttpSession session) {
//Async execution
return new AsyncResult<String>(jsonInString);
}
Now as per my understanding this 'getDataUsingAsyncAndStoreInEhcache' method produces the result independent of the caller method,saves it in ehcache implementation. So, the next time I call an ajax method for getting the data from future, I am doing below implementation -
#RequestMapping(value="getCachedData")
public #ResponseBody String getCachedData(HttpSession session) {
UserLoginSessionInfo userInfo = HttpRequestSessionUtils.getLoggedinUserSessionInfo(session);
Future<String> cachedData = cacheHelper.getDataUsingAsyncAndStoreInEhcache(userInfo, session);
…
return dataFromFuture;
}
Now my expectation is, if any other user logs in after the first user, for him cacheHelper.getDataUsingAsyncAndStoreInEhcache(userInfo, session) should not execute the whole method. Rather it should retrieve the data from Ehcache memory.
But it is not happening. I can assure that caching is working because in the same session if the user calls the getCachedData ajax call multiple times, it is returning the data from cache only. But the issue is happening with the cacheHelper.getDataUsingAsyncAndStoreInEhcache(userInfo, session) implementation (mentioned in the #RequestMapping(value="ehcacheExample") method).
Can you please help me understand why this method is executing in whole, everytime the user calls loadehcacheExamplePage method rather than retrieving it from cache when a second user logging in?
I see different possible issues here. First, there is a bug between caching and async in Spring. However, I don't think it is relevant to you since your CacheHelper and CacheHelperDelegate are not the same object.
Now, it's the Future that will get cached. Not the value returned by it. It's weird. But it should still work.
Finally, Spring uses by default the parameters as the cache key. In your case UserLoginSessionInfo userInfo, HttpSession session. These are highly user specific. So it's normal that for two different users, the method will be called. If that's not what you want, you probably do not need to pass these parameters. You can also tell Spring what to use as a key with #Cacheable(key=...).
I have a Spring controller and two methods that one prepare order data for user to edit, the other merage the latest order information change come back from the form (POST).
Now I want to make some procedure working like OptiMistic Locking without add version column in database. Because client take sometime to edit the order before session expires, I have to make the post method to be sure the order is still on the same status when client first open the order edit page
So I add a orderStatus attribute to carry the original status before order edit page is loaded and compare with the latest status from DB when save the order edit, like this:
#RequestMapping("/order")
#Controller
public class OrderController{
private String orderStatus;
#RequestMapping(value = "/{orderId}/edit", method = RequestMethod.GET)
public String prepareOrderEditForm(....){
....
// remember the original status
orderStatus = Order.getLatestStatus(orderId);
}
#RequestMapping(value = "/processEdit", method = RequestMethod.POST)
public String prepareOrderEditForm(....){
....
String currentOrderStatus = Order.getLatestStatus(orderId);
// compare most recent status with the original status
if(currentOrderStatus.equals(orderStatus) == false){
....//something is wrong, someone may be already edit the order
return "failed";
}else{
orderService.merge(order);
orderStatus = order.getNewStatus();
}
return "updated";
}
}
I understand this design is not as robust as Optimistic locking by database it may ignore the time when DML executed, also the orderStatus here should use getter/setter as java bean. So please exclude above concern but put the scope only within this controller
My question is, as Spring MVC is working in single thread like JSP Servlet, is there any problem while multiple user log on to the spring web application and edit the same order so orderStatus in this controller class somehow intertwined by multiple user(concurrency issue)?
Neither JSP nor Spring MVC are single threaded.
You should always try not to change instance/static variables in your controller methods.
Edit:
I should have mentioned that session thread safety will only work when you set Spring MVC abstract controller's synchronizeOnSession to true (by default it is false).
In your case you can:
1) set synchronizeOnSession to true in your spring application context where you define your controller.
2) Extend from Spring's AbstractController and override handleRequestInternal method.
3) in handleRequestInternal call request.getSession().setAttribute("orderStatus")
I have a design problem as follows: I want to execute several soap webservices, where each response depends on the former.
When all responses are obtained, I want to validate all obtained data, then build some output based on it, and also issue from DB update.
Therefore I created a TemplateFacade method that wrapps all webservices that are to be executed. Problem: I obviously have to persist the responses between the method calls. Which will be problematic as autowired services should by definition be stateless and are singletons.
So how can I use injection with services that have to maintain some kind of state (at least until the Executor.execute() has terminated)?
Could you recommend a better design approach?
#Component
class Executor {
#Autowired
TemplateFacade template;
public void execute() {
template.run();
template.validate();
template.buildOutput();
template.updateDatabase();
}
}
#Service
class TemplateFacade {
//service classes wrapping webservice soap execution logic
#Autowired
PersonSoap personSoap;
#Autowired
CarSsoap carSoap;
#Autowired
ServiceDao dao;
private WebserviceRsp personRsp, carRsp;
void run() {
personRsp = personSoap.invoke();
//process response and prepare CarSoapXML accordingly, then send
carRsp = carSoap.invoke();
}
//the following methods will all
void validate() {
//validate both responses
}
void buildOutput() {
//create system out based on responses
}
void updateDatabase() {
dao.update(..);
}
}
To share state between multiple web services, you could keep track using a PersonState in the session which is tied to the user. I recommend encryption or hashing to secure the information.
When the validate completes, you could keep a PersonState in the session. When the buildOutput starts, you could get the PersonState object and continue with your logic and so on.
It is important that, you keep the PersonState to have a smaller memory footprint. Incase of a lot of data, you could just create a stateObject that will have the necessary state for the next step. e.g. at the end of validate you could create, BuildState object and put it in the session. build will get the object from the session and continue.
But I am not sure if it is really necessary to keep track of state and do it in 2 web services calls. The better solution would be to move all the logic part to another layer, and use the web services as just a window to your business/process layer.
Edit:
One more solution that could work you, is that the response of each step could contain the necessary state that is required for the next step. e.g. validateResponse contains personState and that could somehow be passed for the build.