0

I'm developing an API in Spring Boot and I have two models, a "productModel" and a "categoryModel" that reference each other by ManyToOne and OneToMany.

In product I get a "category" that references the Category model by @ManyToOne, and in category I have a list of products by @OneToMany.

The problem is that when creating my postman request for a product, even sending a category id correctly my category is saved in the database as "null"

productModel

package com.api.business_products_management.models;

import jakarta.persistence.*;

import java.util.UUID;

@Entity
@Table(name = "PRODUCTS")
public class ProductModel {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;

    @Column(nullable = false, length = 80)
    private String product;

    @Column(nullable = true, length = 80)
    private String description;

    @Column(nullable = false, length = 80)
    private Float price;

    @Column(nullable = false, length = 80)
    private Integer stock;

    @ManyToOne
    @JoinColumn(name = "category_id")
    private CategoryModel category;

    public UUID getId() {
        return id;
    }

    public void setId(UUID id) {
        this.id = id;
    }

    public String getProduct() {
        return product;
    }

    public void setProduct(String product) {
        this.product = product;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Float getPrice() {
        return price;
    }

    public void setPrice(Float price) {
        this.price = price;
    }

    public Integer getStock() {
        return stock;
    }

    public void setStock(Integer stock) {
        this.stock = stock;
    }

    public CategoryModel getCategory() {
        return category;
    }

    public void setCategory(CategoryModel category) {
        this.category = category;
    }
}

productDto

package com.api.business_products_management.dtos;

import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import java.util.UUID;

public class ProductDto {
    @NotBlank
    private String product;

    private String description;

    @DecimalMin(value = "0.01", inclusive = true)
    private Float price;

    @NotNull
    @Min(value = 0, message = "Stock must be at least {value}")
    private Integer stock;

    @NotNull
    private CategoryDto category;

    public String getProduct() {
        return product;
    }

    public void setProduct(String product) {
        this.product = product;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Float getPrice() {
        return price;
    }

    public void setPrice(Float price) {
        this.price = price;
    }

    public Integer getStock() {
        return stock;
    }

    public void setStock(Integer stock) {
        this.stock = stock;
    }

    public CategoryDto getCategory() {
        return category;
    }

    public void setCategory(CategoryDto category) {
        this.category = category;
    }
}

productController

package com.api.business_products_management.controllers;

import com.api.business_products_management.dtos.ProductDto;
import com.api.business_products_management.models.ProductModel;
import com.api.business_products_management.services.ProductService;
import jakarta.validation.Valid;
import org.springframework.beans.BeanUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping("/product")
public class ProductController {

    final ProductService productService;

    public ProductController (ProductService productService) {
        this.productService = productService;
    }

    @PostMapping
    public ResponseEntity<Object> saveProduct (@RequestBody @Valid ProductDto productDto) {
        var productModel = new ProductModel();
        BeanUtils.copyProperties(productDto, productModel);
        return ResponseEntity.status(HttpStatus.CREATED).body(productService.save(productModel));
    }
}

categoryModel

package com.api.business_products_management.models;

import jakarta.persistence.*;

import java.util.List;
import java.util.UUID;

@Entity
@Table(name = "CATEGORIES")
public class CategoryModel {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;

    @Column(nullable = false, length = 80)
    private String name;

    @Column(nullable = true, length = 80)
    private String description;

    @OneToMany(mappedBy = "category")
    private List<ProductModel> products;

    public UUID getId() {
        return id;
    }

    public void setId(UUID 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 List<ProductModel> getProducts() {
        return products;
    }

    public void setProducts(List<ProductModel> products) {
        this.products = products;
    }
}

categoryDto

package com.api.business_products_management.dtos;

import com.api.business_products_management.models.ProductModel;
import jakarta.validation.constraints.NotBlank;

import java.util.List;
import java.util.UUID;

public class CategoryDto {

    private UUID id;

    @NotBlank
    private String name;

    @NotBlank
    private String description;

    private List<UUID> productIds;

    public UUID getId() {
        return id;
    }

    public void setId(UUID 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 List<UUID> getProductIds() {
        return productIds;
    }

    public void setProductIds(List<UUID> productIds) {
        this.productIds = productIds;
    }
}

update: I tried to implement the mapStruct as the user replied, but now it returns the error

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 1 of constructor in com.api.business_products_management.controllers.CategoryController required a bean of type 'com.api.business_products_management.mappers.CategoryMapper' that could not be found.


Action:

Consider defining a bean of type 'com.api.business_products_management.mappers.CategoryMapper' in your configuration.


Process finished with exit code 1

Routfin
  • 397
  • 1
  • 11

1 Answers1

0

I reccomend you use ForeignKey

@Entity
@Table(name = "PRODUCTS")
public class ProductModel {
    @ManyToOne
    @JoinColumn(name = "category_id", foreignKey = @ForeignKey(name = "fk_category_id")))
    private CategoryModel category;
}  

Use MapStruct or OrikaMapper instead of BeanUtils for mapping dto to entity.

UPD: Add the dependency

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>${org.mapstruct.version}</version>
</dependency>

Create interface for copy mapping

@Mapper(componentModel = "spring")
public interface ProductMapper {
    ProductModel toEntity(ProductDto dto);
}

Inject a bean to service

@RequiredArgsConstructor
@RestController
@RequestMapping("/product")
public class ProductController {

private final ProductService productService;
private final ProductMapper mapepr;

@PostMapping
public ResponseEntity<Object> saveProduct (@RequestBody @Valid ProductDto productDto) {
    // this code should be in service
    var mapper.toEntity(productDto);
    return ResponseEntity.status(HttpStatus.CREATED).body(productService.save(productModel));
}

}

AbrA
  • 434
  • 1
  • 6
  • 19
  • Why would BeanUtils be causing this? – Routfin Feb 21 '23 at 17:23
  • @MachinLan added a comment. apply these remarks and it should solve the problem – AbrA Feb 22 '23 at 10:14
  • I did exactly that and it's giving me an error. – Routfin Feb 22 '23 at 15:48
  • Description: Parameter 1 of constructor in com.api.business_products_management.controllers.CategoryController required a bean of type 'com.api.business_products_management.mappers.CategoryMapper' that could not be found. Action: Consider defining a bean of type 'com.api.business_products_management.mappers.CategoryMapper' in your configuration. Process finished with exit code 1 – Routfin Feb 22 '23 at 15:52
  • @MachinLan https://stackoverflow.com/questions/59333845/mapstruct-many-to-one-mapping – AbrA Feb 27 '23 at 00:06