Spring boot - Validating request body json key - java

I am working on validating the request body in Spring boot.
When the post controller using the below JSON to create record in the DB. It works fine.
{
"test1": "string",
"test2": "string",
"test3": "string", <--this has #Null in the entity
"test4": "string"
}
However, when one of the key is #NULL in the entity, it will still able to create a record in the DB. I am wondering if there is something that can validate the key and return error.
{
"test1": "string",
"test2": "string",
"test5": "string", <- wrong key by mistake
"test4": "string"
}
Entity class
#Entity
#Table(name = "test")
#Data
#NoArgsConstructor
public class Test implements Serializable {
#Id
#Column(name = "test1")
private String test1;
#Column(name = "test2")
#NotNull
private String test2;
#Column(name = "test3")
private String test3;
#Column(name = "test4")
private String test4;
}

You can use Jackson for parsing JSON and handle unknown properties. It will automatically throw UnrecognizedPropertyException if an unknown property is found as described here

If u want to Validate request body in JSON u can use #Valid
#PostMapping("/books")
Book newBook(#Valid #RequestBody Test test) {
return repository.save(Test);
}
#Column(name = "test3")
#NotNull(message = "Please provide a test3")
private String test3;
if u want on key order
JsonPropertyOrder({ "test1", "test2", "test3", "test4" })
public class Test implements Serializable {
}

Related

Spring boot using Postman returning null when making a POST with foreign key

I'm using POSTMAN and trying to do a post request with Spring Boot. I have an OneToOne relationship between "paciente" and "Operativo". When i try to create an "Operativo" instance with Postman, all the "paciente" attributes asociated with that "Operativo" are null. I don't know if it's problem of the relationship in the models, the controller or something else.
Relation in "paciente" model
#Entity
#Table(name = "Paciente")
public class paciente {
#Id
private long rut;
private String nombre;
private String nacionalidad;
private String sexo;
private String fecha_na;
private String domicilio;
private String diagnostico;
private String telefono;
private String gravedad;
#OneToOne(mappedBy = "paciente")
private Operativo operativo;
Relation in "Operativo" model
#Entity
#Table(name = "Operativo")
public class Operativo{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String dia;
private String hora;
private String equipamiento;
private String equipo;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "paciente", referencedColumnName = "rut")
public paciente paciente;
OperativoController
#RestController
#CrossOrigin
#RequestMapping("/api/Operativo")
public class OperativoController{
#Autowired
private operativoService operativoService;
#GetMapping("")
public Iterable<Operativo> getOperativos(){
return operativoService.listAll();
}
#PostMapping("")
public ResponseEntity<Operativo> addOperativo (#RequestBody Operativo operativo){
Operativo ope= operativoService.saveOrUpdateOperativo(operativo);
return new ResponseEntity<Operativo>(ope,HttpStatus.CREATED);
}
}
operativoService
#Service
public class operativoService{
#Autowired
private OperativoRepository operativoRepository;
public Operativo saveOrUpdateOperativo(Operativo operativo){
return operativoRepository.save(operativo);
}
public Iterable<Operativo> listAll(){
return operativoRepository.findAll();
}
public void deleteOperativo(int id){
operativoRepository.deleteById(id);
}
public Operativo getOperativoById(int id){
return operativoRepository.findById(id);
}
}
POSTMAN post input
{
"id" : 1,
"equipamiento" : "o",
"equipo" : "a",
"hora":"a",
"dia": "a",
"paciente": {"rut":123123}
}
POSTMAN Output
{
"id": 1,
"dia": "a",
"hora": "a",
"equipamiento": "o",
"equipo": "a",
"paciente": {
"rut": 123123,
"nombre": null,
"nacionalidad": null,
"sexo": null,
"fecha_na": null,
"domicilio": null,
"diagnostico": null,
"telefono": null,
"gravedad": null
}
}
1.First thing you don't need referencedColumnName when we are with one to one relationship.
2.Your postman input to be given for "paciente". Above we are seeing only one column data. You have to give input for all neccesary values u need
Finally i fixed my problem, changing this line of code on "Operativo" relation
#JoinColumn(name = "paciente", referencedColumnName = "rut")
to
#JoinColumn(name = "rut")
It seems that "name" attribute should match the name of the foreign key that i'm referencing, instead of the name of the entity referenced.
Now using this POSTMAN Input.
{"id":1 ,
"dia":"asd",
"hora":"asd",
"equipamiento":"asd",
"equipo":"asd",
"paciente":{"rut":19}}
I get the following Output
{
"id": 1,
"dia": "asd",
"hora": "asd",
"equipamiento": "asd",
"equipo": "asd",
"paciente": {
"rut": 19,
"nombre": "asd",
"nacionalidad": "asd",
"sexo": "asd",
"fecha_na": "asd",
"domicilio": "asd",
"diagnostico": "asd",
"telefono": "asd",
"gravedad": "asd"
}
}
Also removed #OneToOne(cascade = CascadeType.ALL) in the "Operativo" class, because it was causing that when i create an "Operativo", it also creates the "Paciente" entity with the given data. So, when creating an "Operativo" if i give it only "paciente":{"rut":19} in the JSON, it will create a "Paciente" with only that attribute and all the others will be null.

Spring boot/Spring data jpa - how to update related entity?

I have following entities:
#Entity
#Table(name = "profile")
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
private ProfileContacts profileContacts;
...
}
and
#Entity
#Table(name = "profile_contacts")
public class ProfileContacts {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "description")
private String description;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
}
I am trying to update it by sending this JSON with update to REST controller:
{
"id": 1,
"description": "an update",
"profileContacts": {
"firstName": "John",
"lastName": "Doe"
}
}
so in the end it calls
profileRepository.save(profile);
where profileRepository is instance of ProfileRepository class:
public interface ProfileRepository extends JpaRepository<Profile, Long> {
}
which is spring-data-jpa interface.
But each time after such update it updates profile table but adds new row to profile_contacts table (table which corresponds to ProfileContactsentity) instead of updating existing ones.
How can I achieve updating?
As per your JSON structure. Yes it will create new profileContacts entry for every time.
The problem every time while saving profile entity you are passing "id": 1 that means Hibernate can identify the entity by this id value (primary key) but for profileContacts mapping you are not sending the id that's why Hibernate considering it has a new entity every time.
To update your profileContacts entity make sure to pass the id of it.
Example:
{
"id": 1,
"description": "an update",
"profileContacts": {
"id" : yourEntityId
"firstName": "John",
"lastName": "Doe"
}
}
Well, that's the expected behavior.
You're not telling hibernate to update the profileContacts.
For the framework to be able to update it, you need to send the profileContact's primary key - which in your case is the ProfileContacts#id.
Something like this:
{
"id": 1,
"description": "an update",
"profileContacts": {
"id": 1
"firstName": "John",
"lastName": "Doe"
}
}
Need to specify the join column in the parent Entity.
#Entity
#Table(name = "profile")
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
**#JoinColumn(name = "id")** //
private ProfileContacts profileContacts;
...
}
Now when you try to save Profile entity it will save the child entity also.
And also needs to include Id in jason request for child entity also
{
"id": 1,
"description": "an update",
"profileContacts": {
"id": 1,
"firstName": "John",
"lastName": "Doe"
}
}
Ok, I see the problem. As #Matheus Cirillo pointed out, you need to tell the hibernate to update the row.
Now, how do you tell the hibernate to update a row - By providing the primary key of the existing row.
But, creating an object with the primary key set is not enough. You need that entity class to be attached to the entity manager and the persistence context.
You can have something like,
//This attaches the entity to the entity manager
ProfileContacts existingProfileContacts = profileContactRepository.getOne(2);
Profile profile = new Profile();
....
....
profile.setProfileContacts(existingProfileContacts);
profileRepository.save(profile);
I hope this helps.

Jackson #JsonIgnoreProperties not working from super class

i have a class named "BaseEntity". other entities extended from Base Entity.
Base Entity
#MappedSuperclass
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#ManyToOne
#JsonIgnoreProperties({"createdBy", "lastModifiedBy", "manager"})
private User createdBy;
private Instant createdDate = Instant.now();
#ManyToOne
#JsonIgnoreProperties({"createdBy", "lastModifiedBy", "manager"})
private User lastModifiedBy;
private Instant lastModifiedDate = Instant.now();
// getters & setters
}
User entity extended from Base Entity.
#Entity
public class User2 extends BaseEntity {
private String userName;
#ManyToOne
#JsonIgnoreProperties({"createdBy", "lastModifiedBy", "manager"})
private User manager;
// getters & setters
}
Error
When i try to serialize user i got this error.
org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class a.b.c.d.domain.User]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle (through reference chain: java.util.Collections$UnmodifiableRandomAccessList[0]->a.b.c.d.domain.User["createdBy"])
if i change only createdBy and lastModifiedBy like this it works. manager user does not cause error
#ManyToOne
#JsonIgnore
private User createdBy;
But this time the json does not contain any createdBy or lastModifiedBy info.
{
"id": 2,
"userName": "admin",
"manager": {
"id": 1,
"username": "system"
}
}
Simply i want to see this json. The first step must contains createdBy and lastModifiedBy.
{
"id": 2,
"userName": "admin",
"manager": {
"id": 1,
"username": "system"
},
"createdBy": {
"id": 1,
"username": "system"
},
"lastModifiedBy": {
"id": 2,
"username": "admin"
}
}
The correct way to use is at the class level.These properties are considered to be ignored in JSON serialization and deserialization.
Example :-
#JsonIgnoreProperties({ "bookName", "bookCategory" })
public class Book {
#JsonProperty("bookId")
private String id;
#JsonProperty("bookName")
private String name;
#JsonProperty("bookCategory")
private String category;
}
If you want to ignore at field level you can use #JsonIgnore
I think #JsonIgnoreProperties should be placed at class level, right before you declare your class.
#JsonIgnoreProperties(ignoreUnknown = true) try using this above your class name.
When we pass true to ignoreUnknown element, then in deserialization if JSON data has a field for which there is no logical property then that JSON field will be ignored and no error will be thrown.
Something like this :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Book {
#JsonProperty("bookId")
private String id;
}

Controller returns JSON containing two entities instead of one

Here is my controller:
// CREATE NEW TODOITEM FROM SENT JSON
#PostMapping("/todos")
#ResponseStatus(HttpStatus.OK)
public ToDoItem newToDo(
#RequestBody ToDoItem toDoItem,
Principal principal
) {
User currentUser = userService.findLoggedInUser(principal);
return toDoItemService.addToDo(toDoItem, currentUser);
}
toDoItemService.addToDo:
public ToDoItem addToDo(ToDoItem toDoItem, User user) {
String toDoTitle = toDoItem.getTitle();
LocalDate toDoDueDate = toDoItem.getDueDate();
ToDoItem newToDo = new ToDoItem(user, toDoTitle, toDoDueDate);
return toDoItemRepository.save(newToDo);
}
ToDoItem entity (ommited constructors and getters/setters):
#Entity
#Table (name = "TO_DO_ITEMS")
public class ToDoItem extends BaseEntity {
#Column(name = "TITLE", nullable = false)
private String title;
#Column(name = "COMPLETED")
private boolean completed;
#Column(name = "DUE_DATE", nullable = false)
#Convert(converter = LocalDateAttributeConverter.class)
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDate dueDate;
// a ToDoItem is only associated with one user
#ManyToOne(cascade=CascadeType.PERSIST)
#JoinColumn(name = "USER_ID")
private User user;
And my toDoItemRepository just extends CrudRepository.
When I shoot:
{
"title":"testtodo3",
"dueDate": [
2015,
12,
6
]
}
at localhost:8080/todos I get this:
{
"id": 1,
"title": "testtodo3",
"completed": false,
"dueDate": [
2015,
12,
6
],
"user": {
"id": 1,
"username": "gruchacz",
"password": "password",
"email": "newUser#example.com"
}
}
Why are all the details of my User visible when I'm returning only ToDoItem (as save from CrudRepository does)? I know my ToDoItem is linked to User, but i would like it to return only ID, title, completed and dueDate WITHOUT user data? I know I could override toString method in ToDoItem entity and return a String from that controller but it's very unelegant and would prefer to retunr just ToDoItem and jackson to handle conversion to JSON.
There are two options for you:
1: add #JsonIgnore on User field in ToDoItem, Jackson will ignore it, or
2: Use DTO pattern, create another value object to deliver it back on HTTP layer.
I will recommend second option

Disable indexing fields for all fields in the document in Elasticsearch

am using elasticsearch in my Java Spring application, for working with elasticsearch Spring JPA is used.
I have a document and corresponding class in java with all fields that should not be indexed (I search through them for exact match using termFilter statement in java api)
In my case I have to annotate each field
#Field(type = FieldType.String, index = FieldIndex.not_analyzed)
and I get something like this
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
#Document(indexName = "message", type = "message")
public class Message implements Serializable {
#Id
#NotNull
#JsonProperty("id")
private String id;
#JsonProperty("userName")
#Field(type = FieldType.String, index = FieldIndex.not_analyzed)
private String userName;
#NotNull
#JsonProperty("topic")
#Field(index = FieldIndex.not_analyzed, type = FieldType.String)
private String topic;
#NotNull
#JsonProperty("address")
#Field(index = FieldIndex.not_analyzed, type = FieldType.String)
private String address;
#NotNull
#JsonProperty("recipient")
#Field(index = FieldIndex.not_analyzed, type = FieldType.String)
private String recipient;
}
Is there a possibility to put annotation on class in order not to duplicate it above all fields?
You can achive your goal without #Field annotations using raw mappings + dynamic templates
Specify the path to your mappings in json file using #Mapping annotation
#Mapping(mappingPath = "/mappings.json")
Then in mappings.json define your mapping like this:
{
"mappings": {
"message": {
"dynamic_templates": [
{ "notanalyzed": {
"match": "*",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"index": "not_analyzed"
}
}
}
]
}
}
}
Note: I didn't test it, so please check for typos.

Categories

Resources