Spring + Hibernate:服务器接收正确的JSON,但Hibernate会插入空值

时间:2017-08-23 13:53:16

标签: java mysql spring hibernate jpa

在服务器端实现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());
        }
    }

}

1 个答案:

答案 0 :(得分:0)

服务器正在获取JSON,但您是否看到在Hibernate托管对象中更新了值?例如,当您开始会话并更新/附加Hibernate托管对象时,只有插入或查询将具有更新的值。

您可以参考What is the proper way to re-attach detached objects in Hibernate?并在将对象附加到会话后检查它是否有效。