How to exclude value null from Put request when mapping to dto - java

I using RestController update data to db but I have problem. When i update value, if value from my update is null , it allways update data to db is null. I dont't want it. I want if 1 field with value is null from my request, i don't want update it.
This bellow my code :
Controller:
RestController
#RequestMapping("/api/products")
#Api(value = "ProductControllerApi",produces = MediaType.APPLICATION_JSON_VALUE)
public class ProductController {
#Autowired
private ProductService productService;
#PatchMapping("/{id}")
public ResponseEntity<ProductResDto> updateProduct(#RequestBody ProductReqDto productReqDto, #PathVariable String id) {
return ResponseEntity.ok(productService.updateProduct(product,id));
}
ProductReqDto:
public class ProductReqDto {
private String name;
private String type;
private String category;
private String description;
private Double prince;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getPrince() {
return prince;
}
public void setPrince(Double prince) {
this.prince = prince;
}
}
ProductResDto:
public class ProductResDto {
private String name;
private String type;
private String category;
private Double prince;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getPrince() {
return prince;
}
public void setPrince(Double prince) {
this.prince = prince;
}
}
MappingDto:
private ProductDto convertToProductDto(ProductReq product) {
return modelMapper.map(product, ProductResDto.class);
}
How to i handle method convertToProductDto only mapping with value not null. Because if , mapping one field : example : product_name = null , it insert to db null. I want if field ProductReq have value, it mapping and keep other different field in database(not set it null if not contain value from ProductReq) .
Example:
**ReqProductDto.class**
private String name;
private String type;
private String category;
private String description;
private Double prince;
but if user only update two field:
private String name;
private String type;
I want spring update field name, and field type user input and keep category,description,prince in my database. In my case, if user update two field: name, and field type,spring update it but spring set category,description,prince is null in my database. I don't want it.
Please help me, thanks.

You've tagged this as spring-boot, so I'm assuming you might be using controllers and validating their parameters. If that is the case, just do
import javax.validation.constraints.NotNull;
public class ProductReqDto {
#NotNull
private String name;
#NotNull
private String type;
#NotNull
private String category;
#NotNull
private String description;
#NotNull
private Double prince;
...
}
and use #Valid for your controllers like this
#PatchMapping("/{id}")
public ResponseEntity<ProductResDto> updateProduct(#RequestBody #Valid ProductReqDto productReqDto, #PathVariable String id) {
return ResponseEntity.ok(productService.updateProduct(product,id));
}
Then your object will be validated on instantiation.

What you want is mainly used for PATCH mapping.
IN a PUT mapping, all fields of an object need to override, but in a PATCH mapping only the fields which are provided needs to be overridden, others need not be changed.
So,
for an existing record,
employee{ employeeId = "A2RTD", empName = "satish", "country": "India"}
And, now one non-mandatory field mobileNo needs to be updated along with the country
DTO request will contain all field other than id, but only country & mobile no will not be null
In this scenario, we can use BeanUtils which is part of spring package
import org.springframework.beans.BeanUtils;
public static Object getDtoMapping(Object source, Object destination) {
BeanUtils.copyProperties(source, destination, getNullFieldNames(source));
return destination;
}
public static String[] getNullFieldNames(Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> fieldNames = new HashSet<>();
for (PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null)
fieldNames.add(pd.getName());
}
String[] result = new String[fieldNames.size()];
return fieldNames.toArray(result);
}
Ths function "getNullFieldNames" will return fieldNames which have value null. So, those fields will not be mapped, as per 3rd optional paramter in BeanUtils
And, you need to pass
// PATCH
EmployeeDao emp = findById(empCode);
emp = (EmployeeDao) getDtoMapping(empUpdateDto, emp);
Here, in BeanUtil copyProperties, 3rd param is optional. If you give it works for PATCH mapping, if you don't give it behaves as PUT mapping.
Since, for PUT mapping, ignoring null as same as not ignoring.
You can use the same in POST, PUT mapping also.
// POST MAPPING
EmployeeDao emp = (EmployeeDao) getDtoMapping(empCreateDto, new Employee());

Related

Api endpoint that edits one element in list

I am currently building a rest api that lets the user enter a recipe and describe it. I am using spring-boot as backend and angularjs as frontend.
This is springboot recipe file
package com.example.springboot;
import com.example.springboot.Recipe;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
#Entity // This tells Hibernate to make a table out of this class
public class Recipe {
public Recipe(){
}
public Recipe(Integer id, String name, String description, String type, Integer preptime, Integer cooktime, String content, Integer difficulty){
this.id = id;
this.name = name;
this.description = description;
this.type = type;
this.preptime = preptimee;
this.cooktime = cooktime;
this.content = content;
this.difficulty = difficulty;
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private String description;
private String type;
private Integer preptime;
#Column(columnDefinition = "TEXT")
private String content;
private Integer difficulty;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Integer getDifficulty() {
return difficulty;
}
public void setDifficulty(Integer difficulty) {
this.difficulty = difficulty;
}
public Integer getpreptime {
return preptime;
}
public void setpreptime(Integer preptime) {
this.preptime = preptime;
}
}
I created an Endpoint where the user can edit the whole recipe. The user can edit the name , description, content and so on in the recipes/edit/{id} endpoint.
The Endpoint looks like this.
#PutMapping("/recipes/edit/{id}")
void updateRecipe(#PathVariable int id, #RequestBody Recipe recipe ) {
System.out.println("entering");
Recipe recipe_ = recipeRepository.findById(id).get();
recipe_.setName(recipe.getName());
recipe_.setDescription(recipe.getDescription());
recipe_.setType(recipe.getType());
recipe_.setpreptime(recipe.getpreptime());
recipe_.setContent(recipe.getContent());
System.out.println("entering " + recipe.getTitle());
System.out.println("entering" + recipe.getType());
System.out.println("entering" + recipe.getDescription());
System.out.println("adding");
recipeRepository.save(recipe_);
}
Now I just want to create an Endpoint which only serves the purpose for renaming the name of the recipe. This putmapping should accept a list as its input then only rename the name of the recipe.
#PutMapping("/recipes/rename")
public List<Recipe> {
System.out.println("entering renaming");
// recipe_.setName(recipe.getName()); ?
}
I don't know how I can implement this. This is what I have come up with so far. An endpoint which takes a list as a parameter.
This is the service.ts file that updates the Recipes in the edit function
service.ts:
updateRecipe (id: number, recipe: any): Observable<any > {
const url = `${this.usersUrl}/edit/${id}`;
return this.http.put(url ,recipe);
}
where the updateRecipe gets called:
save(): void {
const id = +this.route.snapshot.paramMap.get('name');
this.recipeService.updateRecipe2(id, this.recipes)
.subscribe(() => this.gotoUserList());
}
This implementation work , I don't know how I can get it work or how I can rewrite the functions so that it can update only the name of the recipe and not the whole file.
Could someone help me?
Your update the name method should look like that:
#PutMapping("...{id}")
public void updateName(#PathVariable Integer id, #RequestParam String name){
Recipe recipe = repository.findById(id).orElseThrow(...);
recipe.setName(name);
}
if you want to rename list of recipes
public void renameRecipes(String oldName, String newName){
repository.findByName(oldName)
.forEach(r -> r.setName(newName));
}
#PutMapping("recipes/rename")
public void updateNames(#PequestParam String oldName, #RequestParam String newName){
renameRecipes(oldName, newName);
}
Try that.

How to specify a default assembler in seedstack?

I'm using the 19.11 version of seedstack, and I want to use the FluentAssembler assembler to convert an aggregate List into a DTO List.
I'm getting the following error when I call the fluentAssembler.assemble method :
org.seedstack.business.internal.BusinessException: [BUSINESS] Unable to find assembler
Description
-----------
No assembler was found to assemble 'com.inetpsa.svr.domain.model.customer.Customer(Customer.java:1)' to
'com.inetpsa.svr.interfaces.rest.customer.CustomerRepresentation(CustomerRepresentation.java:1)'.
Fix
---
Make sure that an assembler without qualifier exists. If you want to use a qualified assembler (like a default
assembler), specify its qualifier.
I don't know howto specify the qualifier, I'd like to use a default model mapper...
Here is The Resource code :
#Path("customers")
public class CustomerResource {
#Inject
private FluentAssembler fluentAssembler;
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<CustomerRepresentation> listAllCustomers() {
List<Customer> customerList = fetchAllCustomers();
return fluentAssembler.assemble(customerList).toListOf(CustomerRepresentation.class);
}
/**
* Test method - Should be replaced by a repository
* #return List<Customer> all customers
*/
private List<Customer> fetchAllCustomers(){
List<Customer> customerList = new ArrayList<>();
customerList.add(buildCustomer("005","Edward Teach","edward.teach#pirates.org"));
customerList.add(buildCustomer("006","Olivier Levasseur","olivier.levasseur#pirates.org"));
customerList.add(buildCustomer("007","James Bond","james.bond#mi6.uk"));
return customerList;
}
private Customer buildCustomer(String id, String name, String mail){
Customer result = new Customer(id);
result.updateNameAndMail(name, mail);
return result;
}
}
The aggregate :
public class Customer extends BaseAggregateRoot<String> {
#Identity
private String identifier;
private String name;
private String mail;
public Customer(String identifier){
this.identifier=identifier;
}
public void updateNameAndMail(String name, String mail){
if(StringUtils.isBlank(name)){
throw new IllegalArgumentException("Name can't be blank");
}
if(StringUtils.isBlank(mail)){
throw new IllegalArgumentException("Mail can't be blank");
}
this.name=name;
this.mail=mail;
}
public String getIdentifier() {
return identifier;
}
public String getName() {
return name;
}
public String getMail() {
return mail;
}
}
And the DTO :
#DtoOf(Customer.class)
public class CustomerRepresentation {
private String identifier;
private String name;
private String mail;
/**
* Required public no parameters constructor
*/
public CustomerRepresentation(){}
public CustomerRepresentation(String identifier, String name, String mail){
}
#AggregateId
public String getIdentifier() {
return identifier;
}
public String getMail() {
return mail;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public void setMail(String mail) {
this.mail = mail;
}
}
FluentAssembler only takes care of matching a Dto with an Assembler, but does not provide a default implementation of an Assembler by itself.
You have 2 Options to provide a Default Assembler.
Build a class that implements Asselmber
Include an addon that provides that Default Assember for you (As stated on the docs)

Convert pojo (list of object) to json in java using jackson

I'm facing a problem while converting simple JSON to POJO object.
This is my code:
public class Products {
private int id;
private String type;
private String description;
private Double price;
private String brand;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
}
Im getting below as output:
com.ObjectToJson.Product#b97c004
Object to Json --> {
"products" : null
}
I am not able to convert JSON to/from Java Objects using JACKSON API.
Please, can anyone help me out on this?
Where is the code where you try to create the JSON from your object? Without it, it will be hard to say what to fix. The problem may also be coming from the fact that your Products class has no constructor. So when you make an object its nothing. You should add a constructor after you initialize your variables like:
public Products(){
}
Then try making your JSON from the object, which should be something like:
Products p = new Products();
ObjectMapper mapper = new ObjectMapper();
String JSON = mapper.writeValueAsString(p); // to get json string
mapper.readValue(JSON, Products.class); // json string to obj
Be sure to set your Product's object variables using setters or getters, or to initialize them to values by adding parameters to your constructor:
public Products(String type, String description, Double price,
String brand){
this.type = type;
this.description = description; //etc etc...
}

Bean Validation not working for multiple bean classes

I have a bean called Car.java which has the following fields :
private String name;
private String company;
private Maruti maruti;
#NotNull
#NotEmpty
#Size(min=5)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#NotNull
#NotEmpty
#Size(min=6)
public Maruti getMaruti() {
return maruti;
}
public void setMaruti(Maruti maruti) {
this.maruti = maruti;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
where Maruti.java is another bean which has following field :
private String car_name;
private String car_model;
#NotNull
#NotEmpty
#Size(min=6)
public String getCar_name() {
return car_name;
}
public void setCar_name(String car_name) {
this.car_name = car_name;
}
I have added some annotations for validations in both the beans and in my main class I am calling
Set<ConstraintViolation<Car>> violations = validator
.validate(exchange.getIn().getBody(Car.class));
to validate the fields. Now the fields in Car.java are getting validating perfectly, but the annotations in Maruti.java doesn't work. Can anyone tell me why ?
You need to annotate the maruti field or property with #Valid

serializing object using gson and getting LazyInitializationException

This is my Controller method, i am trying to read my database by providing zip, cityname and province name.
#RequestMapping(value = "/retrieve", method = RequestMethod.GET)
public #ResponseBody String retrieveObjectThroughAjax(ModelMap model){
//Calling Service Method to read data according to zip,cityName and province provide
PropertyItems propertyItems=getPropertyTypeandAddressService.readAddressFromZip("H2H-
2N3","Montreal","Quebec");
Gson gson = new Gson();
String json = null;
try{
json = gson.toJson(propertyItems); // serializing object
}catch(Exception e){
logger.error(Constants.METHOD_INSIDE_MESSAGE +"getAuthors",e);
}
logger.debug(json);
return json;
}
}
Service Method
#Service
public class GetPropertyTypeandAddressServiceImpl implements GetPropertyTypeandAddressService{
#Autowired
private GetPropertyTypeandAddressDAO getPropertyTypeandAddressDAO;
#Transactional
public PropertyItems readAddressFromZip(String zipCode,String cityName,String provinceName){
PropertyItems propertyItems=getPropertyTypeandAddressDAO.getAddressFromZip(zipCode, cityName, provinceName);
Hibernate.initialize(propertyItems);
return propertyItems;
}
}
DAO Method
#Repository
public class GetPropertyTypeandAddressDAOimp implements GetPropertyTypeandAddressDAO{
#Autowired
private SessionFactory sessionFactory;
#Override
public PropertyItems getAddressFromZip(String zipCode,String cityName,String provinceName) {
PropertyItems propertyitems = new PropertyItems();
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(PropertyItems.class,"propertyItemsClass");
if(zipCode != null){
criteria.createAlias("propertyItemsClass.address","address");
criteria.add(Restrictions.eq("address.zip",zipCode));
List<PropertyItems> propertyitem = criteria.list();
if(propertyitem.size()>0){
propertyitems = propertyitem.get(0);
}
}
else if(cityName != null){
criteria.createAlias("propertyItemsClass.address","address");
criteria.add(Restrictions.eq("address.city","city"));
criteria.add(Restrictions.eq("city.cityname",cityName));
List<PropertyItems> propertyitem = criteria.list();
if(propertyitem.size()>0){
propertyitems = propertyitem.get(0);
}
}
else if(provinceName != null){
criteria.createAlias("propertyItemsClass.address","address");
criteria.add(Restrictions.eq("address.city","city"));
criteria.add(Restrictions.eq("city.provinces","provinces"));
criteria.add(Restrictions.eq("provinces.provinceName",provinceName));
List<PropertyItems> propertyitem = criteria.list();
if(propertyitem.size()>0){
propertyitems = propertyitem.get(0);
}
}
return propertyitems;
}
}
Console Error
09:53:56,988 ERROR HelloController:567 - Inside Method: getAuthors org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.agilemaple.common.entity.Property.propertyType, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:383)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:375)
As requested my Property Items Look like this
Entity:
Propert Items
#Entity
#Table(name="web_property_item")
public class PropertyItems {
#Id
#GeneratedValue
private Integer id;
private String name;
#ManyToOne
#JoinColumn(name="property_type_id")
private PropertyType propertyType;
#OneToOne(mappedBy="propertyItems",cascade=CascadeType.ALL)
private Address address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public PropertyType getPropertyType() {
return propertyType;
}
public void setPropertyType(PropertyType propertyType) {
this.propertyType = propertyType;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
Entity : Property Type
#Entity
#Table(name="web_property_type")
public class PropertyType {
#Id
#GeneratedValue
private Integer id;
private String name;
#ManyToOne
#JoinColumn(name="property_id")
private Property property;
#OneToMany(fetch = FetchType.LAZY,mappedBy="propertyType", cascade=CascadeType.ALL)
private Set<PropertyItems> propertyItems;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Property getProperty() {
return property;
}
public void setProperty(Property property) {
this.property = property;
}
public Set<PropertyItems> getPropertyItems() {
return propertyItems;
}
public void setPropertyItems(Set<PropertyItems> propertyItems) {
this.propertyItems = propertyItems;
}
}
The problem in hibernate. Your field Set of properties has Lazy fetch method, it means that it will try to get when you call method get of this set. When u calling tojson methods, gson calls all get methods of object but in this moment hibernate session is close and hibernate can't open it in controller. I've faced with the same problem but directly on JSP. In a three weeks i resolved it by one more property for hibernate ( in your case) and I write code to opening session in view interceptor. I'm underground just right now, so I can't show property, but in a hour I will edit this answer and add property.
Added:
I remembered ! property is: hibernate.enable_lazy_load_no_trans = true
If it won't help, I will add code of opensessioninviewinterceptor.
#Override
public void addInterceptors(InterceptorRegistry registry) {
OpenSessionInViewInterceptor sessionInViewInterceptor = new OpenSessionInViewInterceptor();
sessionInViewInterceptor.setSessionFactory(sessionFactory());
}

Categories

Resources