Same Generic commit object getting saved from different instances - javers

I am using Javers version 5.1.2, with jdk 11, in my application, where I am committing Generic Object T and saving into mongodb. The Generic commit objects are actually created from generic rest service, where user can pass any Json.
Every thing is going fine on single instance. Whenever any re commit is sent with same request, Javers commit.getChanges().isEmpty() method returns true.
Issues:
1) Whenever same request to sent to different instance, commit.getChanges().isEmpty() method returns false.
2) If I commit one request, and restart the instance and then again commit, commit.getChanges().isEmpty() again returns false. Instead of true.
As a result of above issue, new version is getting created if request goes to different new instance or instance is restarted.
Could you please let me know, how we can handle this issue.
I will extract code from the project and will create a sample running project and share.
Right now, I can share few classes, please see, if these help:
//---------------------Entitiy Class:
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class ClientEntity<T> {
#Getter
#Setter
private String entityId;
#Getter
#Setter
private T commitObj;
#Getter
#Setter
private String authorName;
#Getter
#Setter
private boolean major;
#Getter
#Setter
private Map<String, String> commitProperties;
}
//--------DataIntegrator
#Service
public class DataIntegrator {
private final Javers javers;
private IVersionRepository versionDao;
private IdGenerator idGenerator;
#Inject
public DataIntegrator(Javers javers, IVersionRepository versionDao, IdGenerator idGenerator) {
this.javers = javers;
this.versionDao = versionDao;
this.idGenerator = idGenerator;
}
public <T> String commit(ClientEntity<T> clientObject) {
CommitEntity commitEntity = new CommitEntity();
commitEntity.setEntityId(clientObject.getEntityId());
commitEntity.setEntityObject(clientObject.getCommitObj());
Map<String, String> commitProperties = new HashMap<>();
commitProperties.putAll(clientObject.getCommitProperties());
commitProperties.put(commit_id_property_key, clientObject.getEntityId());
commitProperties.putAll(idGenerator.getEntityVersions(clientObject.getEntityId(), clientObject.isMajor()));
Commit commit = javers.commit(clientObject.getAuthorName(), commitEntity, commitProperties);
if (commit.getChanges().isEmpty()) {
return "No Changes Found";
}
versionDao.save(
new VersionHead(clientObject.getEntityId(), Long.parseLong(commitProperties.get(major_version_id_key)),
Long.parseLong(commitProperties.get(minor_version_id_key))));
return commit.getProperties().get(major_version_id_key) + ":"
+ commit.getProperties().get(minor_version_id_key);
}
}
1) commitObj is a Generic object, in ClientEntity, which holds Json coming from the Rest webService. The JSON can be any valid json. Can have nested structure also.
2) After calling javers.commit method, we are checking if it is existing entity or there is any change using commit.getChanges().isEmpty().
If same second request goes to same instance, it returns true for change, as expected
If same second request goes to different instance, under load balancer, it takes it as different request and commit.getChanges().isEmpty() returns false. Expected response should be true, as it is same version.
If after first request, I restart instance, and make a same request, it returns false, instead of true, which means, getChanges method taking the same request as same.

Related

How to call oracle stored procedure from specifically spring boot using jdbc

I've a spring boot application which is supposed to call an oracle stored procedure but when I send a request it returns 200 Ok with no payload returned. here is my code on how I called the oracle stored procedure.
#application.properties file
server.port=3000
spring.datasource.url=jdbc:oracle:thin:#xxxxxxxxx
#thin is popular oracle driver, localhost is the host of the database, 1521 is the port of the database, and xe is the database name
spring.datasource.username=XXXXXX
spring.datasource.password=XXXXXX
spring.datasource.driver-class-name= oracle.jdbc.OracleDriver
spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
spring.jpa.hibernate.naming.implicit-strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
spring.jpa.properties.hibernate.proc.param_null_passing=true
#my repo class to call the stored procedure
package com.amsadmacc.amsadmaccadapter.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
#Data
#Entity
#NoArgsConstructor
#Builder
#AllArgsConstructor
#NamedStoredProcedureQuery(
name = "test_stored_proc_sp",
procedureName = "Test_stored_proc"
)
public class PathwaysJourney implements Serializable {
#Id
private long id;
private Integer pidm;
private String firstName;
private String lastName;
private Integer termCode;
private String termDescription;
private Integer applicationNumber;
private String applicationStatusCode;
private String applicationStatusDescription;
private String applicationProgram;
private String majorCode;
private String majorDescription;
private Date applicationDate;
private Integer daysFromApplication;
private String email;
private String mobileNumber;
}
#my controller
#PostMapping("/pathwaysjourney1")
#ResponseBody
public List getAllPathways1() {
spridenRepo.serverOut();
StoredProcedureQuery proc = this.em.createNamedStoredProcedureQuery("Test_stored_proc");
System.out.println("===>>> start exec");
//String output=serverOut();
//log.info("Output {}",output);
proc.execute();
System.out.println("===>>> end exec");
return proc.getResultList();
}
The above end point in the controller returns an empty string like [] in the response body, I've tested the stored procedure in oracle sql developer it returns data.
Any Idea what the problem is? ,some say it is the " set serveroutput on" command, it should be turned on every time a call is made from spring boot, if so, how do we run that command from spring boot whenever the call is made?

Builder class does not have build method (name: 'build') - Jackson

This error occurs when objectMapper.convertValue(cityEntity, City.class)) is called.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Builder class com.example.PostgresApp.dto.City$Builder does not have build method (name: 'build')
package com.example.PostgresApp.dto;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.*;
import org.apache.commons.lang3.StringUtils;
#Value
#Builder(builderClassName = "Builder")
#JsonDeserialize(builder = City.Builder.class)
public class City {
String name;
String description;
#JsonPOJOBuilder(withPrefix = StringUtils.EMPTY)
public static class Builder {
}
}
Service calling repo seems to be where the exception is thrown
public List<City> getCities(){
return cityRepo.findAll().stream().map(cityEntity -> objectMapper
.convertValue(cityEntity, City.class))
.collect(Collectors.toList());
}
The problem is that Jackson cannot deserialize the object value.
My solution was to add the following annotations to my class:
// constructor with no args
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
// constructor with all args
#AllArgsConstructor
// ignore unknown properties during deserialization
#JsonIgnoreProperties(ignoreUnknown = true)
My class ended up looking like this:
#Getter
#Builder
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyClass {
private boolean flag;
private boolean flag2;
private MyClassA objectA;
private MyClassB objectB;
}
If you want to read more on why should we use #NoArgsConstructor and #AllArgsConstructor together, here is a good answer.
Are You sure You always pass name and description to the class Builder?
I got the same error and In my case I was trying to to use a generated Builder to create an Object but I did not pass all of the arguments, so the generated method was not the one spring was looking for. It was searching the N+1 arguments method, but I was passing only N arguments. In this case it will look for a different method signature that can not find.

Strange behavior with SSE endpoint using Redis

I need to push some data to the client if it is in Redis, but client keeps reconnecting to the SSE endpoint every 5 seconds.
The backend code:
#RestController
#RequestMapping("/reactive-task")
public class TaskRedisController {
private final TaskRedisRepository taskRedisRepository;
TaskRedisController(TaskRedisRepository taskRedisRepository){
this.taskRedisRepository = taskRedisRepository;
}
#CrossOrigin
#GetMapping(value = "/get/{id}")
public Flux<ServerSentEvent<Task>> getSseStream2(#PathVariable("id") String id) {
return taskRedisRepository.findByTaskId(id)
.map(task -> ServerSentEvent.<Task>builder().data(task).build());
}
}
#Repository
public class TaskRedisRepository {
public Flux<Task> findByTaskId(String id) {
return template.keys("task:" + id).flatMap(template.opsForValue()::get);
}
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#EqualsAndHashCode
#Entity
public class Task {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(length = 25)
private String result;
}
The client consumes using JS:
var evt = new EventSource("http://localhost:8080/reactive-task/get/98"); evt.onmessage = function(event) {
console.log(event);
};
Can anyone point me in the right direction?
Any advice would be appreciated.
Update: I need to store data for some time (5-10 mins) in Redis.
Update: I wrote similar code on MongoDB and it works fine.
In this case, taskRedisRepository.findByTaskId(id) is probably sending a finite Flux - meaning several elements and a complete signal finishing the stream.
Spring WebFlux will interpret the onComplete signal as the end of the stream and will close it. The default behavior of browser SSE clients is to reconnect right away to the endpoint in case the connection has been terminated.
If you wish to keep a persistent connection and only be notified of new elements being added, you need to leverage that directly as a feature of your datastore. For Redis, this mode is supported with the pub/sub feature (see reference documentation).
To summarize, I think you're seeing the expected behavior as in this case your datastore won't produce an infinite stream notifying you of new elements added to the collection, but rather a finite stream of elements present in that collection at a given time.

Spring WebClient setting some fields to null when they are not null in the response body

I have domain class
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
#Data
#Document
public class Bar {
#Id
private String id;
private List<String> owners;
private List<String> cFeatures;
private Integer age;
private String color;
}
I am using below code to invoke API to get data in Bar object:
import org.springframework.web.reactive.function.client.WebClient;
Mono<Bar> prop = webClient.get()
.uri("/bars/"+id)
.header("Authorization", "Bearer " + access_token)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Bar.class).log("find by id")
The problem is that I get cFeatures as null even though original JSON response
has:
"cFeatures":["custom feature one", ""]
but owners list gets correct value even though owners also has empty string value in the list (not sure if thats the source of this bug)
so Bar object has:
cFeatures: null
Is this a bug in Webclient or am I missing something ? I spent whole day on this but no fix yet.
The problem was with lombok. Lombok was generating setter method:
setCFeatures
but jackson expects setter:
setcFeatures which it does not find and hence null value for cFeatures.
It can be helpful if you make sure your POJO has the correct annotation style. For example, use jsonscheme2pojo.org and choose "Json" as your source type and "Jackson 2.x" as your annotation style. That should make the problem disappear. I was stuck at first by the same problem because I used a Gson-annotated POJO.

Post object in Angular2 to Spring RestContoller using #RequestBody

The connection between client and server works fine and the correct function addEpic is called on the server. The problem is, that just a new instance of Epic is created on the server, but the attributes from the client are not used.
#RequestBody seems to be the problem. Shouldn't convert #RequestBody automatically from the json data to the specified class?
Is the fundamental problem that Epic is a #Entity class?
It could also be that body is wrongly generated at the client.
console.log(body) shows:
{"epic":{"id":"f97d885a-410f-fa6d-7adc-291e63d35341", "name":"OurName"}}
But my swagger-ui shows for body model shema:
{"id":"string", "name":"string"}
Client
addEpic(epic:Epic):Observable<any> {
let body = JSON.stringify({epic});
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers});
return this.http.post(url + "addEpic", body, options)
.map(this.extractHeader)
.catch(this.handleError);
}
export class Epic {
id: string;
name: string;
}
Server
#RequestMapping(value="/addEpic", method = RequestMethod.POST)
public ResponseEntity<Void> addEpic(#RequestBody Epic epic) {
// Here it seems that constructor of epic is called and a new instance is created
epicRepository.saveAndFlush(epic);
return new ResponseEntity<Void>(HttpStatus.CREATED);
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Epic implements Serializable {
private static final long serialVersionUID = -670305953055479441L;
#Column
private String id;
#Column
private String name
}
Your entity Epic has two properties 'id' and 'name'.
In JSON :
{"id":"string", "name":"string"}
This is exactly what Swagger showed you.
So your client is doing it wrong, you should create the JSON there like
let body = JSON.stringify(epic);
Just remove the superflous {} around 'epic'.

Resources