I am using JSR107 caching with Springboot. I have following method.
#CacheResult(cacheName = "books.byidAndCat")
public List<Book> getAllBooks(#CacheKey final String bookId, #CacheKey final BookCategory bookCat) {
return <<Make API calls and get actual books>>
}
First time it makes actual API calls, and second time it loads cache without issue. I can see the following part of log.
Computed cache key SimpleKey [cb5bf774-24b4-41e5-b45c-2dd377493445,LT] for operation CacheResultOperation[CacheMethodDetails ...
But the problem is I want to load cache without making even first API call, Simply needs to fill the cache like below.
String cacheKey = SimpleKeyGenerator.generateKey(bookId, bookCategory).toString();
cacheManager.getCache("books.byidAndCat").put(cacheKey, deviceList);
When I am checking, hashcode of cachekeys are same in both cases, But it is making API calls. If the hashcode is same in both cases, why it is making API calls without considering the cache ?
When debugging spring classes identified that, org.springframework.cache.interceptor.SimpleKeyGenerator is used with the cache key generation even #CacheResult is there.
EDIT and enhance the question :
Apart from that if getAllBooks has overloaded methods, and then call this cached method via separate overloaded method, in that case also method caching is not working.
I'm not an expert of JSR107 annotations in the context of Spring. I use the Spring Cache annotations instead.
When using JSR107, the key used is a GeneratedCacheKey. So that's what you should put in your cache. Not the toString() of it. Note that SimpleKeyGenerator isn't returning a GeneratedCacheKey. It returns a SimpleKey which is the key used by Spring when using its own cache annotations instead of JSR-107. For JSR-107, you need a SimpleGeneratedCacheKey.
Then, if you want to preload the cache, just call getAllBooks before needing it.
If you want to preload the cache in some other way, a #javax.cache.annotation.CachePut should do the trick. See its javadoc for an example.
As #Henri suggested, we can use the cacheput. But for that we need methods. With the below we can update the cache very similar to the cacheput,
//overloaded method both id and cat is available.
List<Object> bookIdCatCache = new ArrayList<>();
bookIdCatCache.add(bookId);
bookIdCatCache.add(deviceCat);
Object bookIdCatCacheKey = SimpleKeyGenerator.generateKey(bookIdCatCache.toArray(new Object[bookIdCatCache.size()]));
cacheManager.getCache("books.byidAndCat").put(bookIdCatCacheKey , bookListWithIdAndCat);
//overloaded method only id is there
List<Object> bookIdCache = new ArrayList<>();
String nullKey = null
bookIdCache.add(bookId);
bookIdCache.add(nullKey);
Object bookIdCacheKey = SimpleKeyGenerator.generateKey(bookIdCache.toArray(new Object[bookIdCache.size()]));
cacheManager.getCache("books.byidAndCat").put(bookIdCacheKey , bookListWithId);
//Not correct(My previous implementation)
String cacheKey = SimpleKeyGenerator.generateKey(bookId, bookCategory).toString();
//Correct(This is getting from spring)
Object cacheKey = SimpleKeyGenerator.generateKey(bookIdCatCache.toArray(new Object[bookIdCatCache.size()]));
Related
I'm creating an order service, new to RestServices world.
I need to read the order model into a OrderDTO and persist in the DB.
For that I have a below method:
#PostMapping(produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<OrderDTO> createOrder(#Valid #RequestBody OrderDTO orderDTO) {
Order order = new Order(orderDTO);
Order createdOrder = orderService.createOrder(order);
OrderDTO createdOrderDTO = new OrderDTO(order);
ResponseEntity<OrderDTO> responseEntity = new ResponseEntity<OrderDTO>(createdOrderDTO, null, HttpStatus.CREATED);
return responseEntity;
}
Everything working fine, but I have concerns about the current design:
I'm reading an input into DTO
To Store the object I'm converting into Order object which will be persisted by Hibernate
Again to send the response back I'm converting the actual order object into DTO.
finally I will create 4-5 Objects per a request, if my app got 100 request it may run into memory issue.
How i can read the model data and persist efficiently?
In general, prefer DTO because of single responsibility principle, every object have its own responsibility and It's also clearer to separate View/Controller from Model objects
You can sometimes reduce OrderDTO, use an object that is both DTD and real Object,
It'll include DTD properties and also other properties that you can add using builder for example, I'm using #JsonIgnoreProperties(ignoreUnknown = true) to set only the DTD properties when object is created from request, e.g.:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(Include.NON_NULL)
public class Order
You can also use JsonGetter/JsonProperty/JsonSetter to control what expected/returned
#JsonGetter and #JsonSetter are old alternatives to #JsonProperty.
I prefer a Mapper like Mapstruct:
OrderDtoMapper mapper = new OrderDTOMapper();
Order order = OrderDtoMapper.map(orderDto, Order.class);
and back:
OrderDTO createdOrderDTO = OrderDtoMapper.map(order, OrderDTO.class);
For me the code looks more readable ... and you do not have much to write for, as Mapstruct maps it automatically. Because it looks like you will map quite a lot ;)
Perhaps a mapper is worth a try: http://mapstruct.org/
I don't see any issue with the design.
As Nizet pointed out. Objects created are short lived.
Normally DTO and Entity design is followed to keep the UI and Service Layer separate.
In this way, you have the option to filter out sensitive info from being passed to the world like password, pin.
But if you want you can use Order entity directly in Controller class.
I won't suggest that but it's possible.
I am using Spring framework and hazelcast cache to cache REST APi at service layer. The api I am caching has #Cacheable annotation with cachename and keygenerator which works fine. I am looking for best way to enable/disable caching using application property or consul property. For that I am trying to pass the property in condition attribute of #Cachable annotation but is not working. With this approach I will end up passing same value in multiple place (wherever I am caching at API level). Is there any good way to handle such operation.
As an example here is a code snippet
#Cacheable(cacheNames = CacheName.MyCache1,keyGenerator = "customKeyGen")
public CachingObject myFirstAPI(String param1, String param2) {
}
Here the hazelcast cache will use customKeyGen and put value (CachingObject) returned by myFirstAPI . If I have to disable this operation , my current approach is to pass some value (read from application property) as condition so that it evaluate the flag/condition before creating cache and cache the value only if the condition is true i.e. cache is enabled, e.g.
#Cacheable(cacheNames = CacheName.MyCache1,keyGenerator = "customKeyGen", condition="${enableCache}")
public CachingObject myFirstAPI(String param1, String param2) {
}
In my case the expression language I am passing in condition throwing exception , which I will figure out why (It is currently throwing SpelEvaluationException, Property or field 'enableCache' cannot be found on object of type 'org.springframework.cache.interceptor.CacheExpressionRootObject' )
My question is , is this correct way to enable/disable caching ? Please suggest.
Try spring.cache.type == none. See https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html#boot-features-caching-provider-none
I need to implement PATCH functionality at my Spring #RestController.
I saw a lot of questions and the most common approach is to use a plain Java Map to do this. Map that allows null helps to solve the issue with null or absent values because it looks like impossible to implement on POJO.
Is there at Spring any out of the box functionality that helps to reflect values from Map to the existing model object or I have to implement it by myself.. for example using Jackson and so on ?
I can share My implementation of PATCH, hope this helps some one in some way. I have a client which has six fields like ( name, type, address fields , ID, Number, postcode), I can edit the client and change anything.
this is also an elaboration on the question with a partial answer ( or a complete one if there is no other way than the two below) Or perhaps PATCH is supposed to be done differently
clientService is just a service which holds the ClientRepository
#RequestMapping(value = "/{id}", method = RequestMethod.PATCH ,produces = {"application/vnd.api+json"} )
ResponseEntity<Resource<Client>> editClient(#PathVariable("id") Integer id,#RequestBody Client editedClientFromBrowser) {
// the id is the ID of the client that I was editing..
//i can use this to retrieve the one from back end
// editedClientFromBrowser is the changed Client Model Object
//from the browser The only field changed is the one
// that was changed in the browser but the rest of
//the object is the same with the ID as well
logger.info("Edit Client reached");
//retreive the same Client from backend and update and save it
// Client client = clientService.getClient(id);
// if (client == null) {
// throw new EntityNotFoundException("Client not found - id: " + id);
// }else{
// change the field that is different from the
//editedClientFromBrowser and then saveAndFlush client
//}
//OR saveAndFlush the editedClientFromBrowser itself
clientService.saveAndFlush(editedClientFromBrowser);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
now another method I read on (http://www.baeldung.com/http-put-patch-difference-spring) and tried:
#RequestMapping(value = "/{id}", method = RequestMethod.PATCH ,
produces = {"application/vnd.api+json"} )
ResponseEntity<Resource<Client>> editClient(#PathVariable("id") Integer id,
#RequestBody Map<String, Object> updates)
this one does give me a hashMap. but it gives me each and every field. even the ones I did not change. So, is that really beneficial? No idea seriously, may be it is lighter than getting the whole client object back.
I would have liked if I get only the hashmap of one or two fields which I did change. that would have been more in line with PATCH i think. Can I improve my two implementations in some way?
Every entity class has user.id value, I have filters on all services which filters data by principal.id and entity user.id on database level, simply adds where clause. I started to using #Cacheable spring option. But filters not works with spring-cache. How can I filter data from cache ?
#Override
#Cacheable(value = "countries")
public List<Country> getAll() {
return countryDao.findAll();
}
Different user has access to values other users if values are in cache.
From documentation
"As the name implies, #Cacheable is used to demarcate methods that are cacheable - that is, methods for whom the result is stored into the cache so on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually execute the method."
In your case you don't have arguments therefore every time getAll is invoked it will return the cached version.
If your countryDao.findAll() inject the userid at database level, you have an issue as the first user calling countryDao.findAll() will cause his result to be cached, therefore other users will get the same result of the first user.
In general, if I understood how you designed the service, it is common that you don't inject the user at db level but pass it at service level so that this is decoupled from the current session (for example a web request).
However if you want to keep like that, it could still work by doing:
#Cacheable(value = "countries", key="#user.id")
public List<Country> getAll(User user) {
return countryDao.findAll();
}
All you have to do is pass the user to the method even if you don't use it explicitly (but the caching will).
I need your expertise :)
I'm working on a application where method calls on a service need to be authenticated.
That means I want each method call to be cached with a key containing the username (to avoid for an unauthorized user to retrieve information cached by an authorized one).
With a personnalized KeyGenerator, all works fine.
Example of my key : username:USERNAME.appVersion:VERSION.METHOD.PARAM1.etc
But at some location, I got methods that retrieve a national content : this one will be the same for each user. And I want to avoid a cache key for each user asking for this content.
Example : appVersion:VERSION.METHOD.PARAM1.etc
So when I'm positioning my #Cacheable annotations, is there any way to set a new parameter in it ? The Key Generator will be able to catch it and know if he had to prefix the cache key name with user information or not.
Thanks for your help :)
Take care
I don't really understand what you're saying by "set a new parameter in it". That parameter should come from somewhere right?
KeyGenerator gives you access to the Method, the actual instance and the method arguments. You may want to have a specific KeyGenerator for this particular cache operation which is something that will be available as from Spring 4.1 but in the mean time you can implement a composite that invokes the right KeyGenerator instance based on the method or, for instance, an annotation you have created to flag it.
Thank you snicoll, that was crystal clear and you really helped me a lot :)
Waiting for Spring 4.1, my team and I decided to use a custom #SharedCache annotation.
Here is some code samples to help if someone is in the same situation.
Given an existing custom GenericKeyGenerator (he's building a custom cache key for each cached method invocation)
We have a new custom AuthenticatedGenericKeyGenerator : he's inherited from GenericKeyGenerator and simply prefixing the cache key with user information
The application is now using AuthenticatedGenericKeyGenerator by default :
<cache:annotation-driven key-generator="keyGenerator"/>
<bean id="keyGenerator" class="your.package.AuthenticatedGenericKeyGenerator" />
AuthenticatedGenericKeyGenerator.java in details :
public class AuthenticatedGenericKeyGenerator extends GenericKeyGenerator {
public AuthenticatedGenericKeyGenerator() {
super(...);
}
#Override
public Object generate(final Object target, final Method method, final Object... params) {
String cacheKey = super.generate(target, method, params).toString();
if(!method.isAnnotationPresent(SharedCache.class)) {
cacheKey = "user:" + some user information + "." + cacheKey;
}
return cacheKey;
}
}
Our custom #SharedCache annotation :
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#Documented
public #interface SharedCache {
}
Now we just have to annotate #Cacheable methods with an extra #SharedCache if we want the cache key to be shared and not be unique (with an user id for example).