Spring Cache Key Using Arguments - spring

I have a class Person with properties id, name and age.
I would like to cache Person object using id and name.
my method is
#Cacheable(value = "person", key = "#p.id + p.name")
getPerson(Person p).
Question is, how do i use cache annotation on getPerson()... something like this.

Using the annotation you could concatenate the values to create a key (I read but have not tested tha the debug symbols may be removed and so the parameter should be referenced as "p0").
#Cacheable(value="person", key="#p0.id.concat(‘:’).concat(#p0.name)")
Otherwise, it will be cached based on Person class equals() and hashCode() just the same way as if you were using the Person object as a key in a Map.
So, for example:
public class Person {
String id;
String name;
Number age;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Person))
return false;
Person other = (Person) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

Related

Spring Cacheable not working with default key

Spring is not caching my function when i am using default key such as -
#PostMapping("getDashboardDataNew")
#Cacheable(value="myDash")
public DashboardDto getHomeDashboardDataNew(#RequestBody DashboardRequest dashboardRequest) {
LOGGER.info(" Get All the Dashboard Information : ");
//code
return dashboardDto;
}
But when I am providing custom key using sPEL its caching the response eg.
#PostMapping("getDashboardDataNew")
#Cacheable(value="myDash", key="#dashboardRequest.level")
public DashboardDto getHomeDashboardDataNew(#RequestBody DashboardRequest dashboardRequest) {
LOGGER.info(" Get All the Dashboard Information : ");
//code
return dashboardDto;
}
The request payload is always-
{"fromDate":null,"toDate":null,"theme":null,"activity":null,"level":1,"levelValue":null,"state":null,"district":null}
Even after auto generating equals and hashcode using eclipse the spring is not caching the value. Below are the auto generated codes
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((activity == null) ? 0 : activity.hashCode());
result = prime * result + ((fromDate == null) ? 0 : fromDate.hashCode());
result = prime * result + ((level == null) ? 0 : level.hashCode());
result = prime * result + ((levelValue == null) ? 0 : levelValue.hashCode());
result = prime * result + ((organizer == null) ? 0 : organizer.hashCode());
result = prime * result + ((theme == null) ? 0 : theme.hashCode());
result = prime * result + ((toDate == null) ? 0 : toDate.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DashboardRequest other = (DashboardRequest) obj;
if (activity == null) {
if (other.activity != null)
return false;
} else if (!activity.equals(other.activity))
return false;
if (fromDate == null) {
if (other.fromDate != null)
return false;
} else if (!fromDate.equals(other.fromDate))
return false;
if (level == null) {
if (other.level != null)
return false;
} else if (!level.equals(other.level))
return false;
if (levelValue == null) {
if (other.levelValue != null)
return false;
} else if (!levelValue.equals(other.levelValue))
return false;
if (organizer == null) {
if (other.organizer != null)
return false;
} else if (!organizer.equals(other.organizer))
return false;
if (theme == null) {
if (other.theme != null)
return false;
} else if (!theme.equals(other.theme))
return false;
if (toDate == null) {
if (other.toDate != null)
return false;
} else if (!toDate.equals(other.toDate))
return false;
return true;
}
I am not changing the request payload.
By default when no key is supplied, Spring cache relies on SimpleKeyGenerator which relies on hashcode of parameters to generate the key. You can check this link.
I figured out what went wrong here.
I was changing one of the property of the request payload somewhere inside the function eg.
dashboardRequest.setLevel(dashboardRequest.getLevel() + 1);
And as spring cache AOP puts the value in cache after the method execution is was using the modified object instead of value provided in param effectively making my key different from the key that would have generated by request payload. Hope this helps someone.

Java 8: Stream and filter based on optional conditions

Example: Filter a list of products that have a price based on fromPrice and toPrice. They could either both be supplied, or just one.
Find all products whose price is greater than fromPrice
Find all products whose price is less than toPrice
Find all products whose price is between fromPrice and toPrice
Product:
public class Product {
private String id;
private Optional<BigDecimal> price;
public Product(String id, BigDecimal price) {
this.id = id;
this.price = Optional.ofNullable(price);
}
}
PricePredicate:
public class PricePredicate {
public static Predicate<? super Product> isBetween(BigDecimal fromPrice, BigDecimal toPrice) {
if (fromPrice != null && toPrice != null) {
return product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(fromPrice) >= 0 &&
product.getPrice().get().compareTo(toPrice) <= 0;
}
if (fromPrice != null) {
return product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(fromPrice) >= 0;
}
if (toPrice != null) {
return product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(toPrice) <= 0;
}
return null;
}
}
Filters:
return this.products.stream().filter(PricePredicate.isBetween(fromPrice, null)).collect(Collectors.toList());
return this.products.stream().filter(PricePredicate.isBetween(null, toPrice)).collect(Collectors.toList());
return this.products.stream().filter(PricePredicate.isBetween(fromPrice, toPrice)).collect(Collectors.toList());
Is there a way to improve my Predicate instead of having the if not null checks? Anything that can be done with optionals?
No, Optional is not designed to replace null checks.
But your code can be improved by avoiding duplication, and by avoiding to return null (which is clearly not a valid value for a Predicate) if both arguments are null:
public static Predicate<Product> isBetween(BigDecimal fromPrice, BigDecimal toPrice) {
Predicate<Product> result = product -> true;
if (fromPrice != null) {
result = result.and(product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(fromPrice) >= 0);
}
if (toPrice != null) {
result = result.and(product -> product.getPrice().isPresent() && product.getPrice().get().compareTo(toPrice) <= 0);
}
return result;
}
You can use Apache Commons Lang, it offers null safe comparison:
ObjectUtils.compare(from, to)
null is assumed to be less than a non-value

spring mvc checking checkbox does not set object

Hi I am new to spring MVC and am trying to implement a form:checkboxes tag and have run into a few issues. All the examples I have googled work with Strings and i want to work with objects so am hoping someone can advise.
I have a List of objects set in my DTO as follows:
TestDTO
private List<Barrier> barriers;
public List<Barrier> getBarriers() {
return barriers;
}
public void setBarriers(List<Barrier> barriers) {
this.barriers = barriers;
}
in my controller class I fetch the barrier objects from the database and add them to my DTO which will be passed to the jsp
savedRecord.setBarriers(dataService.getBarriers());
mav.addObject("testDto", savedRecord);
in my JSP I use the form:checkboxes tag as follows:
<form:checkboxes path="barriers" items="${testDto.barriers}" element="label class='block-label'"
itemLabel="barrier"/>
I also tried with adding
itemValue="id"
but that did not work either
this is wraped in a from element
<form:form method="post" accept-charset="UTF-8" action="${action}"
onsubmit="return checkAndSend()" id="create"
novalidate="" modelAttribute="testDto">
So the issues I am having are as follows:
The checkboxes when displayed all seem to be checked. I have implemented a hashcode and equals method on the barrier object but they still all seem to be checked when I want them unchecked.
Barrier.java
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((barrier == null) ? 0 : barrier.hashCode());
result = prime * result + ((display == null) ? 0 : display.hashCode());
result = prime * result + id;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Barrier other = (Barrier) obj;
if (barrier == null) {
if (other.barrier != null)
return false;
} else if (!barrier.equals(other.barrier))
return false;
if (display == null) {
if (other.display != null)
return false;
} else if (!display.equals(other.display))
return false;
if (id != other.id)
return false;
return true;
}
When I click submit and i look at the testDto my barriers object list is null. How do I get the checked boxes that represent objects to be set on my testDto.
Any pointers and advice is appreciated
Thanks
UPDATE:
Thanks for the pointers. I went with the following. your suggestion helped.
I created the folloiwng in my controller
#InitBinder
public void initBinder(WebDataBinder binder)
{
binder.registerCustomEditor(Barrier.class, new BarrierPropertyEditor(barrierService));
}
and then added a class to do the conversion
public class BarrierPropertyEditor extends PropertyEditorSupport {
private BarrierService barrierService;
public BarrierPropertyEditor(BarrierService barrierService) {
this.barrierService = barrierService;
}
#Override
public void setAsText(String text) {
Barrier b = barrierService.findById(Integer.valueOf(text));
setValue(b);
}
}
This sets the barrier objects on my DTO.
(Sorry for the caps) IT DOES NOT SOLVE WHY THE CHECKBOXES ARE CHECKED ON INITIAL LOAD.
Any ideas how to set the checkboxes unchecked on intitial load?
You can use #ModelAttribute in your Controller to provide the list of values in checkboxes.
#ModelAttribute("barrierList")
public List<Barrier> populateBarrierList() {
List<Barrier> barrierList = dataService.getBarriers();
for(Barrier barrier: barrierList )
{
barrierList.add(barrier);
}
return barrierList ;
}
In JSP, use following :
<form:checkboxes path="barriers" items="${barrierList}" element="label class='block-label'" itemLabel="barrier"/>

Boolean Method for ArrayList

I have created an ArrayList and I am looking to use a boolean method to add an element to the ArrayList.
private static Arraylist <Bicycle> bikelist = new Arraylist<Bicycle>();
public boolean add(Bicycle bicycle)
{
if( bikelist.size() != -1)
return true;
bikelist.add(bicycle);
}
return false;
Would this be plausible? I'm not quite sure how boolean methods work? Do they have to contain an if statement to return true or false?
Using: public boolean add(Object obj): Adds an element obj to the Arraylist. If the element is added successfully, this method returns true
Depends on what you want to return the boolean value for.
For whether the add operation succeeded: the only reason why adding an Object to an ArrayList would fail is due to an OutOfMemoryException AFAIK. Then you would do:
public boolean add(Object obj) {
try {
list.add(obj);
} catch (OutOfMemoryException e) {
return false;
}
return true;
}
If you want to return false if the list already contains the object, then:
public boolean add(Object obj) {
if (list.contains(obj)) {
return false;
}
list.add(obj);
return true;
}
Note that ArrayList<T>.contains(T) checks for object equality using Object.equals(Object). Thus you need to override equals() for your custom class.
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Bicycle other = (Bicycle) obj;
if (other.owner != this.owner) {
return false;
}
if (this.model != other.model) {
return false;
}
return true;
}

Comperator caused "Comparison method violates its general contract!" - 1300 items sort

I have data of 1300 items, sorted with my comperator. Sorting is working fine when I'm using JAVA 6.
When project is run on JAVA 7 I'm getting this exception:
env: JAVA 7, Vaadin 6.8.12, tested with both 32 bit and 64 bit same error occured. ( It is working fine on JAVA 6 )
Caused by: java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeLo(TimSort.java:747)
at java.util.TimSort.mergeAt(TimSort.java:483)
at java.util.TimSort.mergeCollapse(TimSort.java:410)
at java.util.TimSort.sort(TimSort.java:214)
at java.util.TimSort.sort(TimSort.java:173)
at java.util.Arrays.sort(Arrays.java:659)
at java.util.Collections.sort(Collections.java:217)
at com.vaadin.data.util.AbstractInMemoryContainer.doSort(AbstractInMemoryContainer.java:575)
at com.vaadin.data.util.AbstractInMemoryContainer.sortContainer(AbstractInMemoryContainer.java:555)
at com.vaadin.data.util.AbstractBeanContainer.sort(AbstractBeanContainer.java:440)
at com.vaadin.ui.CustomTable.sort(CustomTable.java:4552)
This is comperator which I'm using:
private class StudyRecordComparator implements Comparator<Object> {
#Override
public int compare(Object o1, Object o2) {
if (o1 instanceof String && o2 instanceof String) {
return ((String) o1).compareToIgnoreCase(((String) o2));
}
else if (o1 instanceof QuestionnaireStatusType && o2 instanceof QuestionnaireStatusType) {
QuestionnaireStatusType status1 = (QuestionnaireStatusType) o1;
QuestionnaireStatusType status2 = (QuestionnaireStatusType) o2;
if(status1.equals(status2)) {
return 0;
}
switch(status1) {
case WAITING_FOR_REVIEW :
return -1;
case IN_REVIEW :
if(status2.equals(QuestionnaireStatusType.WAITING_FOR_REVIEW)) {
return 1;
} else {
return -1;
}
case WAITING_PUBLICATION :
if(status2.equals(QuestionnaireStatusType.WAITING_FOR_REVIEW) || status2.equals(QuestionnaireStatusType.IN_REVIEW)) {
return 1;
} else {
return -1;
}
case PUBLISHED :
if(status2.equals(QuestionnaireStatusType.WITHDRAWN)) {
return -1;
} else {
return 11;
}
case WITHDRAWN :
return 1;
}
}
else if (o1 instanceof Date && o2 instanceof Date) {
return ((Date) o1).compareTo(((Date) o2));
} else if (o1 instanceof Integer && o2 instanceof Integer) {
return ((Integer) o1).compareTo(((Integer) o2));
} else if (o1 instanceof User && o2 instanceof User) {
return ((User)o1).toString().compareToIgnoreCase(((User)o2).toString());
}
return 0;
}
}
public enum QuestionnaireStatusType {
IN_PROGRESS("In progress"),
WAITING_FOR_REVIEW("Waiting for review"),
IN_REVIEW("In review"),
WAITING_PUBLICATION("Waiting for publication"),
PUBLISHED("Published"),
WITHDRAWN("Withdrawn");
private final String field;
public String getField() {
return field;
}
QuestionnaireStatusType(String field){
this.field = field;
}
}
Does your collection contain null?
If so, there is one problem with your comparator: It always returns 0 for null, so null is considered equal to everything.
As a result for A > B (premise), you will also have A == null and null == B so by transitivity A and B should also be equal, which violates the premise.
You need to establish a total and consistent ordering for all possible values (including null if that is allowed).
The same issue occurs when your collection contains mixed types (some Strings, some Dates, some QuestionnaireStatusType).

Resources