Hibernate saveOrUpdate-method 创建新的 entry/row 而不是更新现有的

Hibernate saveOrUpdate-method creates new entry/row instead of updating the existing one

我目前正在制作一个网站,为注册用户提供产品管理。它使用 spring + hibernate + mysql + jsp。 Hibernates saveOrUpdate-method 总是为 ProductDetail-entity 创建一个新的 entry/row 到数据库,而不是更新已经存在的实体。我已经根据那里的教程完成了映射,但我不明白是什么让它创建一个新行,因为我在使用之前已经在控制器层中建立了与 Product- 和 ProductDetail-entity (OneToOne) 的关系。有人把我从这场斗争中解救出来,我已经被困在这里3个多月了......下面我将提供实体,控制器和DAO的图片。

产品实体(nvm 注释注释)

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @NotNull(message = "Product name is required")
    @Size(min = 1, message = "is required")
    @Column(name = "product_name")
    private String productName;

    @DecimalMin(value = "0.01", inclusive = true, message = "Price must be a minimum of 0.01$")
    @Digits(integer = 6, fraction = 2, message = "Price out of bounds, limit <6 digits>.<2 digits>")
    @Column(name = "price")
    private float price;

    @NotNull(message = "Quantity is required")
    @Min(value = 1, message = "Must be greater than zero")
    @Column(name = "qty")
    private Integer quantity;

    @NotNull(message = "Email is required")
    @Email(message = "Provide a valid email address")
    @Pattern(regexp = ".+@.+\..+", message = "Provide a valid email address")
    @Column(name = "added_by")
    private String addedBy;

    @Column(name = "creation_datetime")
    private Date createDateTime;


    //@Version
    @Column(name = "last_updated")
    private Date updateDateTime;


    //@Valid
    @OneToOne(mappedBy = "product", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    private ProductDetail productDetail;

    @Column(name = "value_in_stock")
    private float valueInStock;

    public Product() {
        this.createDateTime = new Date();
        this.updateDateTime = this.createDateTime;
    }

    public Product(String productName, float price, Integer quantity, String addedBy) {
        this.productName = productName;
        this.price = price;
        this.quantity = quantity;
        this.addedBy = addedBy;
        this.valueInStock = this.price * this.quantity;
    }

        public void setProductDetail(ProductDetail productDetail) {
        if (productDetail == null) {
            if (this.productDetail != null) {
                this.productDetail.setProduct(null);
            }
        } else {
            productDetail.setProduct(this);
        }
        this.productDetail = productDetail;
    }
// getters and setters

ProductDetail 实体

@Entity
@Table(name = "product_detail")
public class ProductDetail {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @NotNull(message = "A descriptionis required")
    @Column(name = "description")
    private String description;

    @NotNull(message = "Category is required")
    @Column(name = "category")
    private String category;

    @DecimalMin(value = "0.001", inclusive = true, message = "Must a minimum of 0.001g")
    @Digits(integer = 7, fraction = 2, message = "Weight out of bounds, limit <7 digits>.<2 digits>")
    @Column(name = "weight_g")
    private float weight;

    @NotNull(message = "Manufacturer is required")
    @Column(name = "manufacturer")
    private String manufacturer;

    @NotNull(message = "Provide a country")
    @Column(name = "made_in_country")
    private String countryMadeIn;

    @OneToOne(fetch=FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "product_id")
    private Product product;

    public ProductDetail() {

    }

    public ProductDetail(String description, String category, String manufacturer, String madeIn) {
        this.description = description;
        this.category = category;
        this.manufacturer = manufacturer;
        this.countryMadeIn = madeIn;
    }
// Getters and setters...

产品控制器

获取用于添加新产品的表单的方法(GET 请求)

@GetMapping("/add")
public String getAddForm(Model model) {

        // create model attribute to bind all form data
        Product product = new Product();
        ProductDetail productDetail = new ProductDetail();

        // associating product and product details
        product.setProductDetail(productDetail);
        product.getProductDetail());

        model.addAttribute("categoryMap", categoryOptions);
        model.addAttribute("countryMap", countryOptions);
        model.addAttribute("product", product);

        return "product-form";
    }

通过 id 从 productService 获取产品的方法(将数据获取委托给 productDAO)(GET 请求)

@GetMapping("/updateProduct")
public String getUpdateForm(@RequestParam("productId") int productId, Model model) {

        // get product from db
        Product product = productService.getProduct(productId);
        product.getProductDetail());

        // set product as a model to pre-populate the form
        model.addAttribute("categoryMap", categoryOptions);
        model.addAttribute("countryMap", countryOptions);
        model.addAttribute("product", product);

        return "product-form";
    }

处理 saving/updating 产品及其产品详细信息(POST 请求)的方法

@PostMapping("/save")
public String saveOrUpdate(@Valid @ModelAttribute("product") Product product, BindingResult bindingResult,
            Model model) {

        // if result set has errors, return to product form with errors
        if (bindingResult.hasErrors()) {
            model.addAttribute("categoryMap", categoryOptions);
            model.addAttribute("countryMap", countryOptions);
            return "product-form";
        } else {

            // calculate value in stock to product before saving
            product.setValueInStock();

            productService.saveProduct(product);

            return "redirect:/";
        }

    }

ProductDAOImpl

保存或更新给定产品的方法

@Override
    public void saveProduct(Product product) {

        // get current hibernate session
        Session session = sessionFactory.getCurrentSession();

        // save or update given product
        session.saveOrUpdate(product);
    }

通过 id 获取产品的方法

@Override
    public Product getProduct(int id) {

        // get current hibernate session
        Session session = sessionFactory.getCurrentSession();

        Query<Product> query = 
                session.createQuery("SELECT p FROM Product p "
                        + "JOIN FETCH p.productDetail "
                        + "WHERE p.id=:productId",
                        Product.class);

        // set parameters in query
        query.setParameter("productId", id);

        // execute and get product
        Product product = query.getSingleResult();

        return product;
    }

最后是 JSP 表单本身

<form:form action="save" modelAttribute="product" method="POST">

                <!-- associate data with product id -->
                <form:hidden path="id" />

                <div class="form-group row">
                    <label for="nameInput" class="col-sm-2 col-form-label">Product name *:</label>
                    <div class="col-sm-10">
                        <form:input path="productName" cssClass="form-control" id="nameInput" placeholder="Enter name" />
                        <form:errors path="productName" cssClass="errors" />
                    </div>
                </div>

                <div class="form-group row">
                    <label for="priceInput" class="col-sm-2 col-form-label">Price($) *:</label>
                    <div class="col-sm-10">
                        <form:input path="price" cssClass="form-control" id="priceInput" placeholder="Enter price" />
                        <form:errors path="price" cssClass="errors" />
                    </div>
                </div>

                <div class="form-group row">
                    <label for="quantityInput" class="col-sm-2 col-form-label">Quantity *:</label>
                    <div class="col-sm-10">
                        <form:input path="quantity" cssClass="form-control" id="quantityInput" placeholder="Enter qty" />
                        <form:errors path="quantity" cssClass="errors" />
                    </div>
                </div>


                <div class="form-group row">
                    <label for="emailInput" class="col-sm-2 col-form-label">Added by(email) *:</label>
                    <div class="col-sm-10">
                        <form:input path="addedBy" cssClass="form-control" id="emailInput" placeholder="example.address@email.com" />
                        <form:errors path="addedBy" cssClass="errors"  />
                    </div>
                </div>

                <div id="separator" > </div>


                <h5 id="header" >Product Details (Can be updated later)</h5>

                <div class="form-group row">
                    <label for="categoryInput" class="col-sm-2 col-form-label">Category *:</label>
                    <div class="col-sm-3">
                        <form:select path="productDetail.category" id="categoryInput" cssClass="form-control">
                            <form:option value="" label="Select Product Category" />
                            <form:options items="${categoryMap}"/>
                        </form:select>
                        <form:errors path="productDetail.category" cssClass="errors"  />
                    </div>
                </div>

                <div class="form-group row">
                    <label for="manufacturerInput" class="col-sm-2 col-form-label">Manufacturer *:</label>
                    <div class="col-sm-4">
                        <form:input path="productDetail.manufacturer" cssClass="form-control" id="manufacturerInput" placeholder="Enter manufacturer" />
                        <form:errors path="productDetail.manufacturer" cssClass="errors" />
                    </div>

                    <label for="madeInInput" class="col-sm-2 col-form-label">Country *:</label>
                    <div class="col-sm-3">
                        <form:select path="productDetail.countryMadeIn" id="madeInInput" cssClass="form-control">
                            <form:option value="" label="Country manufactured in" />
                            <form:options items="${countryMap}"/>
                        </form:select>
                        <form:errors path="productDetail.countryMadeIn" cssClass="errors"  />
                    </div>
                </div>


                <div class="form-group row">
                    <label for="descriptionInput" class="col-sm-2 col-form-label">Description *:</label>
                    <div class="col-sm-10">
                        <form:textarea path="productDetail.description" cssClass="form-control" id="descriptionInput" placeholder="Short description of product..." />
                        <form:errors path="productDetail.description" cssClass="errors"  />
                    </div>
                </div>


                <div class="form-group row">
                    <label for="weightInput" class="col-sm-2 col-form-label">Weight(g):</label>
                    <div class="col-sm-10">
                        <form:input path="productDetail.weight" cssClass="form-control" id="weightInput" placeholder="Enter weight" />
                    </div>
                </div>

                <input type="submit" value="Add" class="btn btn-primary" />

</form:form>

所以用户应该能够添加和更新产品。目前这只是添加,当用户想要更新产品时,它只是创建一个新的 ProductDetail 实体,而不是更新获取的 Product 实体上的 ProductDetail。

这是因为您使用的是原始数据类型int。将其更新为 Integer,它应该可以正常工作。

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;

更新为,

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

注意:为该字段重新生成相应的getter/setters。

您需要为 productDetail.id 添加隐藏键,否则它会将产品详细信息对象视为临时对象并将其保存为新对象而不是更新它。