0

Trying to refactor my code for the controller below:

 @PutMapping("products/{productId}")
    public ProductResponse updateProduct(@PathVariable("productId") Long productId, @RequestBody ProductForm productForm) {
        Optional<Product> foundProductOpt = productRepository.findById(productId);
        Product foundProduct = foundProductOpt.orElseThrow(() -> new EntityNotFoundException("productId" + productId + "not found."));

        //would like to refactor code below!!

        foundProduct.setProductTitle(productForm.getProductTitle());
        foundProduct.setProductPrice(productForm.getProductPrice());
        foundProduct.setProductDescription(productForm.getProductDescription());
        foundProduct.setProductImage(productForm.getProductImage());
        productRepository.save(foundProduct);

        return new ProductResponse(null, "product updated");
    }

Where I am simply transferring values from a form object into an entity object. Thought of creating a method but did not want to write a method in an entity so not sure what other solutions are out there.

As per request, below is my Product and form object. I would like to use a form object for validation and then have data transferred to the Product object.

Product.java

package com.assignment.restapi.domain;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.Min;
import java.util.Date;

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long productId;
    private String productImage;
    private String productTitle;
    private String productDescription;
    private Integer productPrice;
    //https://stackoverflow.com/questions/49954812/how-can-you-make-a-created-at-column-generate-the-creation-date-time-automatical/49954965#49954965
    @CreationTimestamp
    private Date createdAt;
    @UpdateTimestamp
    private Date updatedAt;

    // default constructor
    public Product() {

    }
    // parameterized constructor-User enetered value goes here to set the fields of the instantiated object.
    public Product(String productImage, String productTitle, String productDescription, Integer productPrice, Date createdAt) {
        this.productImage = productImage;
        this.productTitle = productTitle;
        this.productDescription = productDescription;
        this.productPrice = productPrice;
        this.createdAt = createdAt;
    }
    // getter methods are used to retrieve a value from an object.
    // setter methods are used to set a new value to an object.
    public Long getProductId() {
        return productId;
    }

    public void setProductId(Long productId) {
        this.productId = productId;
    }

    public String getProductImage() {
        return productImage;
    }

    public void setProductImage(String productImage) {
        this.productImage = productImage;
    }

    public String getProductTitle() {
        return productTitle;
    }

    public void setProductTitle(String productTitle) {
        this.productTitle = productTitle;
    }

    public String getProductDescription() {
        return productDescription;
    }

    public void setProductDescription(String productDescription) {
        this.productDescription = productDescription;
    }

    public Integer getProductPrice() {
        return productPrice;
    }

    public void setProductPrice(@Min(value = 1, message = "値段は0以上の値を設定してください。") Integer productPrice) {
        this.productPrice = productPrice;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }

    public Date getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(Date updatedAt) {
        this.updatedAt = updatedAt;
    }
}

ProductForm.java

package com.assignment.restapi.web.view;

import com.assignment.restapi.domain.Product;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

public class ProductForm {

    String productImage;

    @NotBlank(message = "Product title is necessary")
    @Size(max = 100, message = "Product title has to be less than 100 letters")
    String productTitle;

    @Size(max = 500, message = "Product title has to be less than 500 letters")
    String productDescription;

    @Min(value = 1, message = "price has to have a value larger than 1")
    Integer productPrice;

    public ProductForm() {

    }

    public ProductForm(String productImage, String productTitle, String productDescription, Integer productPrice) {
        this.productImage = productImage;
        this.productTitle = productTitle;
        this.productDescription = productDescription;
        this.productPrice = productPrice;
    }

    public String getProductImage() {
        return productImage;
    }

    public void setProductImage(String productImage) {
        this.productImage = productImage;
    }

    public String getProductTitle() {
        return productTitle;
    }

    public void setProductTitle(String productTitle) {
        this.productTitle = productTitle;
    }

    public String getProductDescription() {
        return productDescription;
    }

    public void setProductDescription(String productDescription) {
        this.productDescription = productDescription;
    }

    public Integer getProductPrice() {
        return productPrice;
    }

    public void setProductPrice(Integer productPrice) {
        this.productPrice = productPrice;
    }

    //turns productForm into Product object.
    public Product convertToProduct() {
        //step by step debug mode, new object constructor function in Product.java gets called.
        //setter methods get called and values of the ProductForm object gets passed and becomes the new value of the Product object.
        Product product = new Product();
        product.setProductTitle(this.productTitle);
        product.setProductImage(this.productImage);
        product.setProductDescription(this.productDescription);
        product.setProductPrice(this.productPrice);
        return product;
    }
}
Brett Freeman
  • 451
  • 1
  • 6
  • 8

4 Answers4

2

Using Apache commons-beanutils ::

This would work if you have same field names in both the classes.

@PutMapping("products/{productId}")
public ProductResponse updateProduct(@PathVariable("productId") Long productId, @RequestBody ProductForm productForm) {
    Optional<Product> foundProductOpt = productRepository.findById(productId);
    Product foundProduct = foundProductOpt.orElseThrow(() -> new EntityNotFoundException("productId" + productId + "not found."));
    org.apache.commons.beanutils.BeanUtils.copyProperties(foundProduct, productForm); 
    productRepository.save(foundProduct);
    return new ProductResponse(null, "product updated");
}
  • I tried this but beanutils could not be used...is there anything I have to import? – Brett Freeman Apr 23 '18 at 02:23
  • You need to use **@Validated** annotation in addition to @RequestBody to enable validation. Checkout this snippet :: https://github.com/narayan-sambireddy/stackoverflow_49955529/blob/master/YourController.java – narayan-sambireddy Apr 23 '18 at 08:39
  • still unable as I get "cannot resolve symbol beanutils" – Brett Freeman Apr 24 '18 at 16:54
  • Oh! You need to add commons-beanutils dependency to your maven pom.xml file. https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils/1.9.3 – narayan-sambireddy Apr 25 '18 at 03:56
  • Check out this demo app which will give you more clarity on what needs to be included to get it working. https://github.com/narayan-sambireddy/stackoverflow_49955529 – narayan-sambireddy Apr 25 '18 at 04:19
0

You can use copyProperties(Object dest, Object orig) from BeanUtils

Copy property values from the origin bean to the destination bean for all cases where the property names are the same.

For your controller code,it will look like this(need to pay attention if the objects has Date property and do not have value):

BeanUtils.copyProperties(foundProduct,productForm);

Also you can use Product as a object parameter directly in your controller method,but I think it's not meet your code design

lucumt
  • 7,439
  • 3
  • 18
  • 35
0

Instead of sending productId in @PathVariable send it in body its self. Use Product in @RequestBody instead of ProductForm.

Also add validation in Product entity like @NotNull,@NotBlank and then use @Valid to validate Product request body.

And finally save the Product object. Since the product has id (product id), the repository's save() method will execute a update command on database.

So find code will be like this:

@PutMapping("/products")
public ProductResponse updateProduct(@Valid @RequestBody Product product) {
    Optional<Product> productOptional = productRepository.findById(product.getId());
    Product existingProduct = productOptional
            .orElseThrow(() -> new EntityNotFoundException("productId" + product.getId() + "not found."));

    productRepository.save(product);
    return new ProductResponse(null, "product updated");
}
Ajit Soman
  • 3,586
  • 2
  • 21
  • 40
  • Would like to use the ProductForm object I already have for validation checking and not have to put validation on the Product object. – Brett Freeman Apr 21 '18 at 12:53
  • The reason why i have used `Product` in @RequestBody instead of `ProductForm` is that, to avoid casting from `ProductForm` into `Product`. – Ajit Soman Apr 21 '18 at 13:00
  • Also if you want to hide specific fields from user you can use `@JsonProperty` .Example: `@JsonProperty(access = Access.READ_ONLY)` : Ignore field from request JSON – Ajit Soman Apr 21 '18 at 13:06
  • hmm I still would like to use a form object for validation and keep the entity with not validation annotations. – Brett Freeman Apr 21 '18 at 13:07
  • Can you add your `ProductForm` and `Product` class to your question. You can create a constructor in your Product class which takes `ProductForm` as parameter and will return Product object – Ajit Soman Apr 22 '18 at 15:39
  • @BrettFreeman **Don't** add additional information to an answer. Add it to your question where it belongs! – Neuron - Freedom for Ukraine Apr 23 '18 at 01:48
0

You are really looking for a Mapper functionality. You can of course write it yourself (i.e. a Spring bean called ProductEntityToDTOMapper with methods toEntity(dto) and toDto(entity) or use a Java library for that: ModelMapper is one that supports Spring integration and another one is MapStruct that also provides Spring integration.

dimitrisli
  • 20,227
  • 12
  • 57
  • 63