How to generate cache CustomKey for Redis in Spring Boot - spring-boot

I have spring boot application which is integrated with Redis cache. Have to implement caching for one of the method call. That method argument is an object with multiple params which is external Request object. This object params will vary for each request also based on that param and its values output of the method is varies. I need to create a cache key using that Request object field/param values. How to achieve it.
We can use SimpleKeyGenerator only when method params are static?
UserService.java
#Cacheable(value = "usercache", keyGenerator="customKeyGenerator")
public UserResponse getUserResp(User user){
//Some backend calls
return user
}
User.java
public class User {
private String firstname;
private String lastname;
private Integer age;
private Date dob;
private Address address;
// Another 10 params
}
In this method implementation User object is dynamic. I have to create a cache key based on User object fields which is having valid non null values. How to achieve it.

I have implemented as like below.
User.java
public class User implements Serializable {
private String firstname;
private String lastname;
private Integer age;
private Date dob;
private Address address;
// Another 10 params
#Override
public int hashCode() {
final int prime = 31;
//Add necessary fields
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
//Add necessary fields
}
}
public class UserKeyGenerator implements KeyGenerator{
private static final String UNDERSCORE_DELIMITER = "_";
#Override
public Object generate(Object target, Method method, Object... params) {
String cacheKey = null;
if(params.length > 0) {
StringJoiner paramStrJoiner = new StringJoiner(UNDERSCORE_DELIMITER);
User userReq = (User) params[0];
paramStrJoiner.add(target.getClass().getSimpleName());
paramStrJoiner.add(method.getName());
paramStrJoiner.add(String.valueOf(userReq.hashCode()));
cacheKey = paramStrJoiner.toString();
}
return cacheKey;
}

Related

Jackson: Multiple Serializers on the same entity when differents Rest EndPoint are called

I'm trying to avoid using the DTO antipattern when different EndPoint are called, where each returns a distinct representation of the same entity. I'd like to take advantage of the serialization that Jackson performs when I return the entity in the Rest EndPoint. This means that serialization is only done once and not twice as it would be with a DTO (entity to DTO and DTO to Json):
EndPoints example:
#GetMapping("/events")
public ResponseEntity<List<Event>> getAllEvents(){
try {
List<Event> events = (List<Event>) eventsRepository.findAll();
return new ResponseEntity<List<Event>>(
events, HttpStatus.OK);
}catch(IllegalArgumentException e) {
return new ResponseEntity<List<Event>>(HttpStatus.BAD_REQUEST);
}
}
#GetMapping("/events/{code}")
public ResponseEntity<Event> retrieveEvent(#PathVariable String code){
Optional<Event> event = eventsRepository.findByCode(code);
return event.isPresent() ?
new ResponseEntity<Event>(event.get(), HttpStatus.OK) :
new ResponseEntity<Event>(HttpStatus.BAD_REQUEST);
}
Serializer (class that extends of StdSerializer):
#Override
public void serialize(Event value, JsonGenerator gen,
SerializerProvider provider) throws IOException {
if(firstRepresentation) {
//First Representation
gen.writeStartObject();
gen.writeNumberField("id", value.getId());
gen.writeObjectField("creation", value.getCreation());
gen.writeObjectFieldStart("event_tracks");
for (EventTrack eventTrack : value.getEventsTracks()) {
gen.writeNumberField("id", eventTrack.getId());
gen.writeObjectField("startTime", eventTrack.getStartTime());
gen.writeObjectField("endTime", eventTrack.getEndTime());
gen.writeNumberField("priority", eventTrack.getPriority());
gen.writeObjectFieldStart("user");
gen.writeNumberField("id", eventTrack.getUser().getId());
gen.writeEndObject();
gen.writeObjectFieldStart("state");
gen.writeNumberField("id", eventTrack.getState().getId());
gen.writeStringField("name", eventTrack.getState().getName());
gen.writeEndObject();
}
gen.writeEndObject();
gen.writeEndObject();
}else if(secondRepresentation) {
//Second Representation
}
}
Entity:
#JsonSerialize(using = EventSerializer.class)
#RequiredArgsConstructor
#Getter
#Setter
public class Event implements Comparable<Event>{
private Long id;
#JsonIgnore
private String code;
private Timestamp creation;
#NonNull
private String description;
#JsonUnwrapped
#NonNull
private EventSource eventSource;
#NonNull
private String title;
#NonNull
private Category category;
#NonNull
#JsonProperty("event_tracks")
private List<EventTrack> eventsTracks;
#JsonProperty("protocol_tracks")
private List<ProtocolTrack> protocolTracks;
public void addEventTrack(#NonNull EventTrack eventTracks) {
eventsTracks.add(eventTracks);
}
#JsonIgnore
public EventTrack getLastEventTrack() {
return eventsTracks.get(eventsTracks.size() - 1);
}
#JsonIgnore
public int getLastPriority() {
return getLastEventTrack().getPriority();
}
public void generateUUIDCode() {
this.code = UUID.randomUUID().toString();
}
#Override
public int compareTo(Event o) {
return this.getLastPriority() - o.getLastPriority();
}
}
So, so far I have been able to serialize a representation type with a class that extend of StdDeserializer, but this doesn't give me the flexibility to extend the representations of the same entity attributes in multiple ways. Although I've tried it with Json annotations, but I realize that the more representations the entity class has, it can get very complex, something that it should be simple. Maybe some idea how I could do it.
Thank you.
If you want to define multiple representations of the same bean you could use Jackson JsonView.
With json views you can set different strategies to define which property will be serialized in the response and so use different views by endpoint.
Documentation here : https://www.baeldung.com/jackson-json-view-annotation
Just don't forget that you doing REST here....avoid expose too many representations of the same resource

JHipster - Insert in the database with the GET method

I have to create an application with Jhipster but i never use it before.
When a user send a GET request to the address http://localhost:8080/api/newmesure/{mac-address}/{value}
I want to insert a new mesure in my database.
First i created 3 entity "Plantes", "Capteurs" and "Mesures" with this format :
Image here : https://i.stack.imgur.com/zJqia.png (I'm not allowed to post)
I activated the JPA Filtering to create a #Query to insert data in my database but i read that was not possible.
In /src/main/java/com/mycompany/myapp/web/rest/MesuresRessources.java :
/**
* REST controller for managing {#link com.mycompany.myapp.domain.Mesures}.
*/
#RestController
#RequestMapping("/api")
public class MesuresResource {
private final Logger log = LoggerFactory.getLogger(MesuresResource.class);
private static final String ENTITY_NAME = "mesures";
#Value("${jhipster.clientApp.name}")
private String applicationName;
private final MesuresService mesuresService;
private final MesuresQueryService mesuresQueryService;
public MesuresResource(MesuresService mesuresService, MesuresQueryService mesuresQueryService) {
this.mesuresService = mesuresService;
this.mesuresQueryService = mesuresQueryService;
}
#GetMapping("/newMesure/{mac}/{value}")
public String newMesure(#PathVariable String mac,#PathVariable int value) {
log.debug("Adresse MAC : "+mac);
log.debug("Valeur : "+value);
#Query("SELECT valeur FROM Mesures WHERE id = 1") //not working
Mesures getValeur(); //not working
return "Mesure ajoutée";
}
}
In /src/main/java/com/mycompany/myapp/domain/Mesures.java :
/**
* A Mesures.
*/
#Entity
#Table(name = "mesures")
public class Mesures implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "valeur")
private Integer valeur;
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties("macs")
private Capteurs mac;
// jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getValeur() {
return valeur;
}
public Mesures valeur(Integer valeur) {
this.valeur = valeur;
return this;
}
public void setValeur(Integer valeur) {
this.valeur = valeur;
}
public Capteurs getMac() {
return mac;
}
public Mesures mac(Capteurs capteurs) {
this.mac = capteurs;
return this;
}
public void setMac(Capteurs capteurs) {
this.mac = capteurs;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Mesures)) {
return false;
}
return id != null && id.equals(((Mesures) o).id);
}
#Override
public int hashCode() {
return 31;
}
#Override
public String toString() {
return "Mesures{" +
"id=" + getId() +
", valeur=" + getValeur() +
"}";
}
}
Louan
Learning java with JHipster is probably not a wise idea, it uses a very rich technology stack which might lose you unless you invest enough time to learn the basics.
There are many things wrong in your code and approach:
You can't use #Query annotation inside the body of method a of your REST controller, it must be used in your #Repository interface, this code can't compile. See https://www.baeldung.com/spring-data-jpa-query for a quick introduction
JPA filtering is not related to inserting into database
In HTTP/REST, GET method is supposed to be idempotent. For making changes in your database you should use POST or PUT methods. See What is idempotency in HTTP methods?
Your entity naming convention is not consistent: use singular for entity classes because each entity object represents one single instance of Mesure. Here you have Plantes (plural), Capteur (singular) and Mesures (plural). For table names, JHipster uses singular but plural is quite common too because a table holds many rows. Of course, this is just a convention and you or your team may decide to apply another (like a prefix for table names) but the key point is to be consistent.

What is the CLI command to view inside of a set data type in redis

I user a CRUDRepository in my spring data redis project to persist a redis hash in my redis cluster. i have rest api written to persist and get thte values of the data. this works fine.
however my entity annotated with RedisHash is being saved as a set / and i am not able to look inside the value using redis cli.
how do i look inside a set data type(without popping) in redis cli
i looked at redis commands page https://redis.io/commands#set
i only get operations which can pop value . i neeed to simply peek
EDIT:
to make things clearer, i am using spring crudrepo to save the user entity into redis data store. the user entity gets saved as a set data type.
when i query back the user details, i can see entire details of the user
{
userName: "somak",
userSurName: "dattta",
age: 23,
zipCode: "ah56h"
}
i essentially want to do the same using redis cli... but all i get is
127.0.0.1:6379> smembers user
1) "somak"
how do i look inside the somak object.
#RestController
#RequestMapping("/immem/core/user")
public class UserController {
#Autowired
private UserRepository userRepository;
#RequestMapping(path = "/save", method = RequestMethod.GET, produces = "application/json")
#ResponseStatus(HttpStatus.OK)
public void saveUserDetails() {
User user = new User();
user.setAge(23);
user.setUserName("somak");
user.setUserSurName("dattta");
user.setZipCode("ah56h");
userRepository.save(user);
}
#RequestMapping(path="/get/{username}", method = RequestMethod.GET, produces = "application/json")
public User getUserDetails(#PathVariable("username") String userName) {
return userRepository.findById(userName).get();
}
}
#Repository
public interface UserRepository extends CrudRepository<User, String>{
}
#RedisHash("user")
public class User {
private #Id String userName;
private #Indexed String userSurName;
private #Indexed int age;
private String zipCode;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserSurName() {
return userSurName;
}
public void setUserSurName(String userSurName) {
this.userSurName = userSurName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
}
I don't understant your descr with your problem, but I understand your title.
In redis set, the member is always string type.
I hope you can offer more info about UserRepository.save:
User user = new User();
user.setAge(23);
user.setUserName("somak");
user.setUserSurName("dattta");
user.setZipCode("ah56h");
userRepository.save(user);
And you can check your redis data and check data type when rest api invoked.

Spring Data JPA JpaRepository only uses No Arg Constructor

I have this simple REST API that i created with Spring Boot.
In this app, I have a a POJO called Expense with 4 fields. I have a no Argument constructor and another constructor that takes only two inputs. One String value "item" and one Integer value "amount". The date is set using the LocalData.now() method and the id is set automatically in a MySql db running in the server.
Here's my Entity class
#Entity
public class Expense {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
private Integer id;
private String date;
private String item;
private Integer amount;
//No Arg Construction required by JPA
public Expense() {
}
public Expense(String item, Integer amount) {
this.date = LocalDate.now().toString();
this.item = item;
this.amount = amount;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
}
I have another class with RestController annotation where i have set a method to post Expense object with a post method using Request Mapping annotation.
#RestController
public class ExpController {
private ExpService expService;
private ExpenseRepo expenseRepo;
#Autowired
public ExpController(ExpService expService, ExpenseRepo expenseRepo) {
this.expService = expService;
this.expenseRepo = expenseRepo;
}
#RequestMapping(path = "/addExp", method=RequestMethod.POST)
public void addExp(Expense expense){
expenseRepo.save(expense);
}
}
Now finally i am using PostMan to make the HTTP Post Request. I have made a simple Json Format text to send Item and Amount
{
"item":"Bread",
"amount": 75
}
After I make the post request, all i can see is that a new Entry is created but all values are set to null.
I have done some experimentation and found out that the expenseRepo.save(expense) method is only using the default no Arg constructor to save the data. But it's not using the second constructor that takes the two parameters that I am passing through Postman
How to solve this issue. Please help
Change your controller method like this
#RequestMapping(path = "/addExp", method=RequestMethod.POST)
public void addExp(#RequestBody Expense expense){
expenseRepo.save(expense);
}
You need to use #RequestBody

Hibernate -validator group sequence provider getDefaultSequenceProvider gets null as input

I am using the hibernate validator group sequence and want to execute the groups in a sequence based on business rules. But the input to the groupSequenceProvider for its getValidationGroups is always null, and hence custom sequence never gets added.
My request object:
#GroupSequenceProvider(BeanSequenceProvider.class)
public class MyBean {
#NotEmpty
private String name;
#NotNull
private MyType type;
#NotEmpty(groups = Special.class)
private String lastName;
// Getters and setters
}
Enum type:
public enum MyType {
FIRST, SECOND
}
My custom sequence provider:
public class BeanSequenceProvider implements DefaultGroupSequenceProvider<MyBean> {
#Override
public List<Class<?>> getValidationGroups(MyBean object) {
final List<Class<?>> classes = new ArrayList<>();
classes.add(MyBean.class);
if (object != null && object.getType() == MyType.SECOND) {
classes.add(Special.class);
}
return classes;
}
}
Group annotation:
public interface Special {
}
When I execute the above code, I get the input MyBean object as null and cannot add the custom sequence. What am I missing? I am using hibernate-validator version as 5.4.1.Final

Resources