Spring boot caching in #Service class does not work - caching

I have problems with save some values in #Service method.
My code:
#Service(value = "SettingsService")
public class SettingsService {
...
public String getGlobalSettingsValue(Settings setting) {
getTotalEhCacheSize();
if(!setting.getGlobal()){
throw new IllegalStateException(setting.name() + " is not global setting");
}
GlobalSettings globalSettings = globalSettingsRepository.findBySetting(setting);
if(globalSettings != null)
return globalSettings.getValue();
else
return getGlobalEnumValue(setting)
}
#Cacheable(value = "noTimeCache", key = "#setting.name()")
public String getGlobalEnumValue(Settings setting) {
return Settings.valueOf(setting.name()).getDefaultValue();
}
My repository class:
#Repository
public interface GlobalSettingsRepository extends CrudRepository<GlobalSettings, Settings> {
#Cacheable(value = "noTimeCache", key = "#setting.name()", unless="#result == null")
GlobalSettings findBySetting(Settings setting);
It should work like this:
get value form DB if data exist,
if not save value from enum.
but it didn't save any data from DB or enum.
My cache config:
#Configuration
#EnableCaching
public class CacheConfig {
#Bean
public EhCacheCacheManager cacheManager(CacheManager cm) {
return new EhCacheCacheManager(cm);
}
#Bean
public EhCacheManagerFactoryBean ehcache() {
EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
return ehCacheManagerFactoryBean;
}
}
I have some example to make sure that cache is working in my project in rest method:
#RequestMapping(value = "/system/status", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> systemStatus() {
Object[] list = userPuzzleRepository.getAverageResponseByDateBetween(startDate, endDate);
...
}
public interface UserPuzzleRepository extends CrudRepository<UserPuzzle, Long> {
#Cacheable(value = "averageTimeAnswer", key = "#startDate")
#Query("select AVG(case when up.status='SUCCESS' OR up.status='FAILURE' OR up.status='TO_CHECK' then up.solvedTime else null end) from UserPuzzle up where up.solvedDate BETWEEN ?1 AND ?2")
Object[] getAverageResponseByDateBetween(Timestamp startDate, Timestamp endDate);
and it's work well.
What am I doing wwrong?

You have two methods in your SettingsService, one that is cached (getGlobalEnumValue(...)) and another one that isn't cached, but calls the other method (getGlobalSettingsValue(...)).
The way the Spring cache abstraction works however is by proxying your class (using Spring AOP). However, calls to methods within the same class will not call the proxied logic, but the direct business logic beneath. This means caching does not work if you're calling methods in the same bean.
So, if you're calling getGlobalSettingsValue(), it will not populate, nor use the cache when that method calls getGlobalEnumValue(...).
The possible solutions are:
Not calling another method in the same class when using proxies
Caching the other method as well
Using AspectJ rather than Spring AOP, which weaves the code directly into the byte code at compile time, rather than proxying the class. You can switch the mode by setting the #EnableCaching(mode = AdviceMode.ASPECTJ). However, you'll have to set up load time weaving as well.
Autowire the service into your service, and use that service rather than calling the method directly. By autowiring the service, you inject the proxy into your service.

The problem is in the place you call your cacheable method from. When you call your #Cacheable method from same class, you just call it from this reference, which means it doesn't wrapped by Spring's proxy, so Spring can't catch your invocation to handle it.
One on ways to solve this problem is to #Autowired service to itself and just call methods you expected spring have to handle by this reference:
#Service(value = "SettingsService")
public class SettingsService {
//...
#Autowired
private SettingsService settingsService;
//...
public String getGlobalSettingsValue(Settings setting) {
// ...
return settingsSerive.getGlobalEnumValue(setting)
//-----------------------^Look Here
}
#Cacheable(value = "noTimeCache", key = "#setting.name()")
public String getGlobalEnumValue(Settings setting) {
return Settings.valueOf(setting.name()).getDefaultValue();
}
}
But if you have such problems it means your classes are take on too much and aren't comply with the principle of "single class - single responsibility". The better solution would be to move method with #Cacheable to dedicated class.

Related

Spring #Value not working in Spring Boot 2.5.5, getting null values

I am trying to inject some property values into variables by means of Spring #Value annotation but I get null values. I tried different configurations and triks but it doesn't work. Think is that before today everythink was working properly. I do not know what I changed in order to get things broken.
Here is my java class:
#Component
#ConditionalOnProperty(prefix = "studioghibli", name = "get")
public class StudioGhibliRestService {
#Value("${studioghibli.basepath}")
private static String BASE_PATH;
#Value("${studioghibli.path}")
private static String PATH;
#Value("${studioghibli.protocol:http}")
private static String PROTOCOL;
#Value("${studioghibli.host}")
private static String HOST;
private static String BASE_URI = PROTOCOL.concat("://").concat(HOST).concat(BASE_PATH).concat(PATH);
#Autowired
StudioGhibliRestConnector connector;
public List<StudioGhibliFilmDTO> findAllFilms() throws SipadContenziosoInternalException {
var response = connector.doGet(BASE_URI, null, null);
if (!response.getStatusCode().is2xxSuccessful() || !response.hasBody()) {
throw new SipadContenziosoInternalException(Errore.INTERNAL_REST_ERROR, "FindAll(), microservizio ".concat(BASE_URI), null);
}
return (List<StudioGhibliFilmDTO>) response.getBody();
}
}
As you can see, the class is annotated with #Component, that because I will need to use it as #Service layer in order to make a rest call in my business logic.
The class is also annotaded with conditional on property...
Here is a screenshot of the debug window at startup:
Since the PROTOCOL value is null, i get a null pointer exception immediately at start up.
Here is part of the application-dev.properties file:
studioghibli.get
studioghibli.protocol=https
studioghibli.host=ghibliapi.herokuapp.com
studioghibli.basepath=/
studioghibli.path=/films
First of all, #Value annotation does not work with static fields.
Secondly, fields with #Value annotation is processed when the instance of the class (a bean) is created by Spring, but static fields exist for a class (for any instance), so when the compiler is trying to define your static BASE_URI field other fields are not defined yet, so you get the NPE on startup.
So you might need a refactoring, try to inject values with the constructor like this:
#Component
#ConditionalOnProperty(prefix = "studioghibli", name = "get")
public class StudioGhibliRestService {
private final StudioGhibliRestConnector connector;
private final String baseUri;
public StudioGhibliRestService(StudioGhibliRestConnector connector,
#Value("${studioghibli.basepath}") String basePath,
#Value("${studioghibli.path}") String path,
#Value("${studioghibli.protocol:http}") String protocol,
#Value("${studioghibli.host}") String host) {
this.connector = connector;
this.baseUri = protocol.concat("://").concat(host).concat(basePath).concat(path);
}
// other code
}
Thanks, It works for me, I have to add some codes to my project. Then I check the spring core document in "#Value" section. Besides
When configuring a PropertySourcesPlaceholderConfigurer using
JavaConfig, the #Bean method must be static.
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}

How to mock beans Autowired on service layer in #WebMvcTest

I am testing a REST API's in Spring boot gradle app, my mocked service using #MockBean is returning null. This mocked service return null if there are some beans Autowired in service class(I used constructor injection).
Here is sample Code(Not compiled, only for understanding)
#RestController
#RequestMapping("/xxx")
class TestController {
private RetriveDataService retriveDataService;
public TestControllerx(RetriveDataService retriveDataService) {
this.retriveDataService = retriveDataService;
}
#PostMapping(value = "/yyy")
public MyResponseModel myMethod(#RequestBody MyRequestModel model) {
return retriveDataService.retriveData(model);
}
}
#Service
class RetriveDataService {
private TokenService tokenService;
public RetriveDataService(TokenService tokenService) {
this.tokenService = tokenService;
}
public MyResponseModel retriveData(MyRequestModel model) {
String accessToken = tokenService.getToken().getAccessToken();
return retriveData(model, accessToken);
}
}
#RunWith(SpringRunner.class)
#WebMvcTest(TestController.class)
public class TestControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private RetriveDataService retriveDataService;
#Test
public void testRetriveData() throws Exception {
mvc.perform(MockMvcRequestBuilders.post("/xxx/yyy").content(objectMapper.writeValueAsString(new MyRequestModel()))
.contentType(MediaType.APPLICATION_JSON_UTF8)).andDo(MockMvcResultHandlers.print())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
}
}
When I run this test, i am getting following output(If my service do not need another bean, I am getting expected output)
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Due to this response i facing problem on line .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));. also when i check response body(as body is also a null)
Sample project to reproduce the issue is here
Checking your repository confirmed assumption form the discussion in comments under question.
You specify expectations on your mock
MyModel requestMessage = new MyModel();
requestMessage.setMessage("Hello Request Post");
given(testService1.getMessage(requestMessage)).willReturn(responseMessage);
but the message received to in your controller in your #WebMvcTest is not equal to requestMessage specified in the test. This is due to the fact that MyModel class does not override equals method.
In this situation, Mockito will use its default behaviour:
By default, for all methods that return a value, a mock will return either null, a primitive/primitive wrapper value, or an empty collection, as appropriate. For example 0 for an int/Integer and false for a boolean/Boolean.
You have two options to fix the problem:
override equals (and hashCode) in your request class.
Get acquainted with argument matchers
More info on option 2.:
Technically, your expectation is equivalent to:
given(testService1.getMessage(ArgumentMatchers.eq(requestMessage)))
.willReturn(responseMessage);
You can use other matcher, or even define your own. This is useful if you cannot modify code of your argument's type (type coming from 3-rd party library etc).
For example, you can use ArgumentMatchers.any(MyModel.class))

Custom serialization of single #RestController endpoint

Is there a way (preferably some type of annotation) to register a custom serializer for a single endpoint in a #RestController? Extending the bean and putting a #JsonSerialize on it would be an option, but that demands an otherwise pretty useless new bean class. I tried the following:
#JsonSerialize(using = CustomSerializer.class)
#RequestMapping(value = "/some_endpoint/", method = RequestMethod.GET)
public SomeType someEndpoint() {
return someObject;
}
But the #JsonSerialize annotation doesn't appear to have any meaning to Spring in that context. Is there an alternative or is the extra bean class my only option?
You can use #JsonView(View.Summary::class) in the attributes you want to add or ignore and in the method you want to apply that view, for example:
public class View {
public interface Summary
}
public class A{
#JsonView(View.Summary.class)
private String serialized = "",
private String notSerialized = ""}
and then in the controller:
#JsonView(View.Summary.class)
#GetMapping("/")
#ResponseBody
public A getA(){
return A()
}
If you want to reverse the JsonView (serialize the atributtes who doesnt have the view). you can add the following propertie: spring.jackson.mapper.default-view-inclusion=true

Spring AOP: How to read path variable value from URI template in aspect?

I want to create Spring aspect which would set method parameter, annotated by custom annotation, to an instance of a particular class identified by an id from URI template. Path variable name is parameter of the annotation. Very similar to what Spring #PathVariable does.
So that controller method would look like:
#RestController
#RequestMapping("/testController")
public class TestController {
#RequestMapping(value = "/order/{orderId}/delete", method = RequestMethod.GET)
public ResponseEntity<?> doSomething(
#GetOrder("orderId") Order order) {
// do something with order
}
}
Instead of classic:
#RestController
#RequestMapping("/testController")
public class TestController {
#RequestMapping(value = "/order/{orderId}/delete", method = RequestMethod.GET)
public ResponseEntity<?> doSomething(
#PathVariable("orderId") Long orderId) {
Order order = orderRepository.findById(orderId);
// do something with order
}
}
Annotation source:
// Annotation
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface GetOrder{
String value() default "";
}
Aspect source:
// Aspect controlled by the annotation
#Aspect
#Component
public class GetOrderAspect {
#Around( // Assume the setOrder method is called around controller method )
public Object setOrder(ProceedingJoinPoint jp) throws Throwable{
MethodSignature signature = (MethodSignature) jp.getSignature();
#SuppressWarnings("rawtypes")
Class[] types = signature.getParameterTypes();
Method method = signature.getMethod();
Annotation[][] annotations = method.getParameterAnnotations();
Object[] values = jp.getArgs();
for (int parameter = 0; parameter < types.length; parameter++) {
Annotation[] parameterAnnotations = annotations[parameter];
if (parameterAnnotations == null) continue;
for (Annotation annotation: parameterAnnotations) {
// Annotation is instance of #GetOrder
if (annotation instanceof GetOrder) {
String pathVariable = (GetOrder)annotation.value();
// How to read actual path variable value from URI template?
// In this example case {orderId} from /testController/order/{orderId}/delete
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder
.currentRequestAttributes()).getRequest();
????? // Now what?
}
} // for each annotation
} // for each parameter
return jp.proceed();
}
}
UPDATE 04/Apr/2017:
Answer given by Mike Wojtyna answers the question -> thus it is accepted.
Answer given by OrangeDog solves the problem form different perspective with existing Spring tools without risking implementation issue with new aspect. If I knew it before, this question would not be asked.
Thank you!
If you already have access to HttpServletRequest, you can use HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE spring template to select map of all attributes in the request. You can use it like that:
request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE)
The result is a Map instance (unfortunately you need to cast to it), so you can iterate over it and get all the parameters you need.
The easiest way to do this sort of thing is with #ModelAttribute, which can go in a #ControllerAdvice to be shared between multiple controllers.
#ModelAttribute("order")
public Order getOrder(#PathVariable("orderId") String orderId) {
return orderRepository.findById(orderId);
}
#DeleteMapping("/order/{orderId}")
public ResponseEntity<?> doSomething(#ModelAttribute("order") Order order) {
// do something with order
}
Another way is to implement your own PathVariableMethodArgumentResolver that supports Order, or register a Converter<String, Order>, that the existing #PathVariable system can use.
Assuming that it is always the first parameter bearing the annotation, maybe you want to do it like this:
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import de.scrum_master.app.GetOrder;
#Aspect
#Component
public class GetOrderAspect {
#Around("execution(* *(#de.scrum_master.app.GetOrder (*), ..))")
public Object setOrder(ProceedingJoinPoint thisJoinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
Annotation[][] annotationMatrix = methodSignature.getMethod().getParameterAnnotations();
for (Annotation[] annotations : annotationMatrix) {
for (Annotation annotation : annotations) {
if (annotation instanceof GetOrder) {
System.out.println(thisJoinPoint);
System.out.println(" annotation = " + annotation);
System.out.println(" annotation value = " + ((GetOrder) annotation).value());
}
}
}
return thisJoinPoint.proceed();
}
}
The console log would look like this:
execution(ResponseEntity de.scrum_master.app.TestController.doSomething(Order))
annotation = #de.scrum_master.app.GetOrder(value=orderId)
annotation value = orderId
If parameter annotations can appear in arbitrary positions you can also use the pointcut execution(* *(..)) but this would not be very efficient because it would capture all method executions for each component in your application. So you should at least limit it to REST controlers and/or methods with request mappings like this:
#Around("execution(#org.springframework.web.bind.annotation.RequestMapping * (#org.springframework.web.bind.annotation.RestController *).*(..))")
A variant of this would be
#Around(
"execution(* (#org.springframework.web.bind.annotation.RestController *).*(..)) &&" +
"#annotation(org.springframework.web.bind.annotation.RequestMapping)"
)

How to define Spring Data Repository scope to Prototype?

I'm using Spring data jpa & hibernate for data access along with Spring boot. All the repository beans are singleton by default. I want to define the scope of all my repositories to Prototype. How can I do that?
#Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
Edit 1
The problem is related to domain object being shared in 2 different transactions which is causing my code to fail. I thought it is happening because repository beans are singleton. That's the reason I asked the question. Here is the detailed explanation of the scenario.
I have 2 entities User and UserSkill. User has 1-* relationship with UserSkills with lazy loading enabled on UserSkill relation.
In a UserAggregationService, I first make a call to fetch an individual user skill by id 123 which belongs to user with id 1.
public class UserAggregationService {
public List<Object> getAggregatedResults() {
resultList.add(userSkillService.getUserSkill(123));
//Throws Null Pointer Exception. See below for more details.
resultList.add(userService.get(1));
}
}
Implementation of UserSkillService method looks like
#Override
public UserSkillDTO getUserSkill(String id) {
UserSkill userSkill = userSkillService.get(id);
//Skills set to null avoid recursive DTO mapping. Dozer mapper is used
//for mapping.
userSkill.getUser().setSkills(null);
UserSkillDTO result = mapper.map(userSkill, UserSkillDTO.class);
return result;
}
In the call of user aggregation service, I call UserService to fetch userDetails. UserService code looks like
#Override
public UserDTO getById(String id) {
User user = userService.getByGuid(id);
List<UserSkillDTO> userSkillList = Lists.newArrayList();
//user.getSkills throws null pointer exception.
for (UserSkill uSkill : user.getSkills()) {
//Code emitted
}
....
//code removed for conciseness
return userDTO;
}
UserSkillService method implementation
public class UserSkillService {
#Override
#Transactional(propagation = Propagation.SUPPORTS)
public UserSkill get(String guid) throws PostNotFoundException {
UserSkill skill = userSkillRepository.findByGuid(guid);
if (skill == null) {
throw new SkillNotFoundException(guid);
}
return skill;
}
}
UserService method implementation:
public class UserService {
#Override
#Transactional(readOnly = true)
public User getByGuid(String guid) throws UserNotFoundException {
User user = userRepo.findByGuid(guid);
if (user == null) {
throw new UserNotFoundException(guid);
}
return user;
}
}
Spring boot auto configuration is used to instantiate entity manager factory and transaction manager. In the configuration file spring.jpa.* keys are used to connect to the database.
If I comment the below line of code, then I do not get the exception. I am unable to understand why change in the domain object is being affecting the object fetch in a different transaction.
userSkill.getUser().setSkills(null);
Please suggest If I have missed something.

Resources