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
  • 571
  • 1
  • 6
  • 8

3 Answers3

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

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,926
  • 3
  • 22
  • 41
  • 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 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,895
  • 12
  • 59
  • 63