0

I encountered a very annoying problem while realizing a project with Spring + Hibernate on server side, with MySQL as DBMS.

There are two main entities that are involved in this problem, Product and Feedback, connected with a relation One-To-Many (so one product can have some feedbacks, but a feedback can relate only to one product). It's like a classic structure of products with user feedbacks.

This is the implementation in Spring of the two entities:

Product.java

package it.uniroma2.e01.model;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyEnumerated;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import it.uniroma2.e01.constant.Category;
import lombok.Data;

@Entity
@Data
@Table(name = "product")
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false, unique = true)
    private String name;

    private String description;

    @Column(nullable = false, length = 1)
    @MapKeyEnumerated(EnumType.ORDINAL)
    private Category category;

    @ManyToOne(cascade = CascadeType.MERGE)
    @JoinColumn(name = "product_user_id", nullable = false)
    private User user;

    @OneToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE})
    @JoinTable(name = "product_feedback",
        joinColumns = {@JoinColumn(name = "product_id")},
        inverseJoinColumns = {@JoinColumn(name = "feedback_id")}
    )
    private List<Feedback> feedbacks;

    public Product() {}

}

Feedback.java

package it.uniroma2.e01.model;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyEnumerated;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import it.uniroma2.e01.constant.Rating;
import lombok.Data;

@Entity
@Data
@Table(name = "feedback")
public class Feedback {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(cascade = CascadeType.MERGE)
    @JoinColumn(name = "feedback_user_id", nullable = false)
    private User user;

    @Column(nullable = false, length = 1)
    @MapKeyEnumerated(EnumType.ORDINAL)
    private Rating rating;

    private String text;

    @OneToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE})
    @JoinTable(name = "feedback_comment",
        joinColumns = {@JoinColumn(name = "feedback_id")},
        inverseJoinColumns = {@JoinColumn(name = "comment_id")}
    )
    private List<Comment> comments;

    public Feedback() {}

}

When I try to insert a new feedback related to a product updating an existing product what it happens is that Hibernate correctly understand the operation to do, but it tries to insert a feedback with all null values (excepting the id field, that is auto-generating).

For example, I have the following product already in the DB:

Product(
    id=20, 
    name=Antipasto misto, 
    description=Misto di antipasti, 
    category=APPETIZER, 
    user=User(
        id=1, 
        email=lukinho89@hotmail.it, 
        username=Luca, 
        password=ciao
    ), 
    feedbacks=[]
)

and with a PUT request from client I try to update it. The product object received (in JSON), print on the console on server side, is correctly:

Product(
    id=20, 
    name=Antipasto misto, 
    description=Misto di antipasti, 
    category=APPETIZER, 
    user=User(
        id=1, 
        email=lukinho89@hotmail.it, 
        username=Luca, 
        password=ciao
    ), 
    feedbacks=[
        Feedback(
            id=null, 
            user=User(
                id=1, 
                email=lukinho89@hotmail.it, 
                username=Luca, 
                password=ciao
            ), 
            rating=GOOD, 
            text=Prova, 
            comments=[]
        )
    ]
)

and Hibernate tries correctly to insert the new feedback, but instead of binding the right values it tries to bind all null values, triggering an error. This is the log:

2017-08-23 15:49:24.720 DEBUG 1479 --- [nio-8080-exec-7] org.hibernate.SQL                        : insert into feedback (rating, text, feedback_user_id) values (?, ?, ?)
Hibernate: insert into feedback (rating, text, feedback_user_id) values (?, ?, ?)
2017-08-23 15:49:24.720 TRACE 1479 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [null]
2017-08-23 15:49:24.720 TRACE 1479 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [BIGINT] - [null]
2017-08-23 15:49:24.725  WARN 1479 --- [nio-8080-exec-7] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1048, SQLState: 23000
2017-08-23 15:49:24.725 ERROR 1479 --- [nio-8080-exec-7] o.h.engine.jdbc.spi.SqlExceptionHelper   : Column 'rating' cannot be null
2017-08-23 15:49:24.728 DEBUG 1479 --- [nio-8080-exec-7] .m.m.a.ExceptionHandlerExceptionResolver : Resolving exception from handler [public org.springframework.http.ResponseEntity<it.uniroma2.e01.dto.ProductDTO> it.uniroma2.e01.controller.ProductController.updateProduct(it.uniroma2.e01.dto.ProductDTO,java.lang.Long)]: org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
2017-08-23 15:49:24.729 DEBUG 1479 --- [nio-8080-exec-7] .w.s.m.a.ResponseStatusExceptionResolver : Resolving exception from handler [public org.springframework.http.ResponseEntity<it.uniroma2.e01.dto.ProductDTO> it.uniroma2.e01.controller.ProductController.updateProduct(it.uniroma2.e01.dto.ProductDTO,java.lang.Long)]: org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
2017-08-23 15:49:24.730 DEBUG 1479 --- [nio-8080-exec-7] .w.s.m.s.DefaultHandlerExceptionResolver : Resolving exception from handler [public org.springframework.http.ResponseEntity<it.uniroma2.e01.dto.ProductDTO> it.uniroma2.e01.controller.ProductController.updateProduct(it.uniroma2.e01.dto.ProductDTO,java.lang.Long)]: org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
2017-08-23 15:49:24.750 DEBUG 1479 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet        : Could not complete request

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement

I don't really know how to resolve it, apparently everything is fine (for me, at least).

Thanks a lot in advance for every help I will receive!

Edit:

I add the controller:

ProductController.java

package it.uniroma2.e01.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import it.uniroma2.e01.constant.Category;
import it.uniroma2.e01.dto.ProductDTO;
import it.uniroma2.e01.service.ProductService;

@RestController
public class ProductController {

    @Autowired
    private ProductService productService;

    @RequestMapping(path = "e01/product/findById/{id}", method = RequestMethod.GET)
    public ResponseEntity<ProductDTO> findProductById(@PathVariable Long id) {
        ProductDTO productDTO = productService.findById(id); 
        ResponseEntity<ProductDTO> resultProductDTO = 
                new ResponseEntity<ProductDTO>(productDTO, HttpStatus.OK);
        return resultProductDTO;
    }

    @RequestMapping(path = "e01/product/findByName/{name}", method = RequestMethod.GET)
    public ResponseEntity<ProductDTO> findProductByName(@PathVariable String name) {
        ProductDTO productDTO = productService.findByName(name); 
        ResponseEntity<ProductDTO> resultProductDTO = 
                new ResponseEntity<ProductDTO>(productDTO, HttpStatus.OK);
        return resultProductDTO;
    }

    @RequestMapping(path = "e01/product/findByCategory/{category}", method = RequestMethod.GET)
    public ResponseEntity<ProductDTO> findProductById(@PathVariable Category category) {
        ProductDTO productDTO = productService.findByCategory(category); 
        ResponseEntity<ProductDTO> resultProductDTO = 
                new ResponseEntity<ProductDTO>(productDTO, HttpStatus.OK);
        return resultProductDTO;
    }

    @RequestMapping(path = "e01/product/findByUserId/{id}", method = RequestMethod.GET)
    public ResponseEntity<List<ProductDTO>> findByUserId(@PathVariable Long id) {
        List<ProductDTO> productsDTO = productService.findByUserId(id); 
        ResponseEntity<List<ProductDTO>> resultProductsDTO = 
                new ResponseEntity<List<ProductDTO>>(productsDTO, HttpStatus.OK);
        return resultProductsDTO;
    }

    @RequestMapping(path = "e01/product/getAll", method = RequestMethod.GET)
    public ResponseEntity<List<ProductDTO>> findAll() {
        List<ProductDTO> productsDTO = productService.findAll(); 
        ResponseEntity<List<ProductDTO>> resultProductsDTO = 
                new ResponseEntity<List<ProductDTO>>(productsDTO, HttpStatus.OK);
        return resultProductsDTO;
    }

    @RequestMapping(path = "e01/product/create", method = RequestMethod.POST)
    public ResponseEntity<ProductDTO> createProduct(@RequestBody ProductDTO productDTO) {
        ProductDTO productCreated = productService.createProduct(productDTO);
        ResponseEntity<ProductDTO> resultProductDTO = 
                new ResponseEntity<ProductDTO>(productCreated, HttpStatus.OK);
        return resultProductDTO;
    }

    @RequestMapping(path = "e01/product/update/{id}", method = RequestMethod.PUT)
    public ResponseEntity<ProductDTO> updateProduct(@RequestBody ProductDTO productDTO, @PathVariable Long id) {
        ProductDTO productUpdated = productService.updateProduct(productDTO);
        ResponseEntity<ProductDTO> resultEmployee;
        if(productUpdated != null) {
            resultEmployee = new ResponseEntity<ProductDTO>(productUpdated, HttpStatus.OK);
        } else {
            resultEmployee = new ResponseEntity<ProductDTO>(HttpStatus.NOT_MODIFIED);
        }
        return resultEmployee;
    }

    @RequestMapping(path = "e01/product/delete/{id}", method = RequestMethod.DELETE)
    public ResponseEntity<ProductDTO> deleteProduct(@PathVariable Long id) {
        productService.deleteProduct(id);
        ResponseEntity<ProductDTO> response = new ResponseEntity<ProductDTO>(HttpStatus.OK);
        return response;
    }

}

and the service layer:

ProductServiceImpl.java

package it.uniroma2.e01.service.impl;

import java.lang.reflect.Type;
import java.util.List;

import org.modelmapper.ModelMapper;
import org.modelmapper.TypeToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import it.uniroma2.e01.constant.Category;
import it.uniroma2.e01.dao.ProductDAO;
import it.uniroma2.e01.dto.ProductDTO;
import it.uniroma2.e01.model.Product;
import it.uniroma2.e01.service.ProductService;

@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    private ProductDAO productDAO;

    @Autowired
    private ModelMapper mapper;

    public ProductDTO findById(Long id) {
        Product product = productDAO.findById(id);
        return mapper.map(product, ProductDTO.class);
    }

    public ProductDTO findByName(String name) {
        Product product = productDAO.findByName(name);
        return mapper.map(product, ProductDTO.class);
    }

    public ProductDTO findByCategory(Category category) {
        Product product = productDAO.findByCategory(category);
        return mapper.map(product, ProductDTO.class);
    }

    public List<ProductDTO> findByUserId(Long id) {
        List<Product> products = productDAO.findByUserId(id);
        Type listProductsType = new TypeToken<List<ProductDTO>>() {}.getType();
        return mapper.map(products, listProductsType);
    }

    public List<ProductDTO> findAll() {
        List<Product> products = productDAO.findAll();
        Type listProductsType = new TypeToken<List<ProductDTO>>() {}.getType();
        return mapper.map(products, listProductsType);
    }

    public ProductDTO createProduct(ProductDTO productDTO) {
        Product product = mapper.map(productDTO, Product.class);
        Product productFromDB = productDAO.save(product);
        return mapper.map(productFromDB, ProductDTO.class);
    }

    public ProductDTO updateProduct(ProductDTO productDTO) {
        Product product = mapper.map(productDTO, Product.class);
        Product productFromDB;
        if(productDAO.exists(product.getId())) {
            System.out.println(product.toString());
            productFromDB = productDAO.save(product);
        } else {
            productFromDB = null;
        }
        return mapper.map(productFromDB, ProductDTO.class);
    }

    public void deleteProduct(Long id) {
        Product product = productDAO.findById(id);
        if(product != null) {
            productDAO.delete(product.getId());
        }
    }

}
Luca
  • 1
  • 1
  • Are you sure your `rating` field is provided correctly? Did you try the debugging mode? – Pijotrek Aug 23 '17 at 13:56
  • Yes, if I try only to create a feedback with the same values of the example I wrote before it executes the operation without any kind of problem. If you want some parts of code tell me and I will copy them – Luca Aug 23 '17 at 14:30
  • please share your controller and business layer.Log your object fields in hibernate part, be sure that you pass filled object to the business layer. – Sarkhan Aug 23 '17 at 15:42
  • Shared, thanks in advance! I checked, the object fields arrives correctly in the business layer – Luca Aug 24 '17 at 16:21
  • You don't have a "add feedback" operation, which would appear to be a mandatory operation for a system that allows people to add feedback to a product. The act of providing feedback in no way updates a product, it simply adds feedback. – DwB Aug 24 '17 at 16:35
  • You are right DwB, beyond my problem there was an error in how I structured the server architecture. Thanks for the help! – Luca Sep 02 '17 at 09:53

1 Answers1

0

Server is getting JSON but are you seeing values updated in Hibernate managed Object? For instance when you begin session and update /attach Hibernate managed objects then only the inserts or queries will have updated value.

You can refer What is the proper way to re-attach detached objects in Hibernate? and check if it works after attaching the object to session.

Amit Mahajan
  • 895
  • 6
  • 34