在服务器端实现Spring + Hibernate项目时,我遇到了一个非常恼人的问题,MySQL作为DBMS。
此问题涉及两个主要实体,即产品和反馈,与一对多关系相关联(因此一个产品可以有一些反馈,但反馈只能与一个产品相关)。它就像是具有用户反馈的经典产品结构。
这是两个实体在Spring中的实现:
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() {}
}
当我尝试插入与更新现有产品的产品相关的新反馈时,Hibernate正确理解要执行的操作,但它尝试插入带有所有空值的反馈(除了id字段,是自动生成的。)
例如,我在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=[]
)
并且来自客户端的PUT请求我尝试更新它。收到的产品对象(以JSON格式),在服务器端的控制台上打印,是正确的:
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=[]
)
]
)
并且Hibernate正确地尝试插入新的反馈,但是它不是绑定正确的值而是尝试绑定所有空值,从而触发错误。这是日志:
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
我真的不知道如何解决它,显然一切都很好(至少对我而言)。
非常感谢我将获得的每一项帮助!
编辑:
我添加控制器:
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;
}
}
和服务层:
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());
}
}
}
答案 0 :(得分:0)
服务器正在获取JSON,但您是否看到在Hibernate托管对象中更新了值?例如,当您开始会话并更新/附加Hibernate托管对象时,只有插入或查询将具有更新的值。
您可以参考What is the proper way to re-attach detached objects in Hibernate?并在将对象附加到会话后检查它是否有效。