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());
}
}
}