Hibernate saveOrUpdate-метод создает новую запись / строку вместо обновления существующей - PullRequest
0 голосов
/ 04 мая 2019

В настоящее время я создаю веб-сайт для управления продуктами для зарегистрированного пользователя. Он использует Spring + Hibernate + MySQL + JSP. Hibernates saveOrUpdate-метод всегда создает новую запись / строку в базе данных для сущности ProductDetail-сущности вместо обновления уже существующей. Я сделал сопоставление в соответствии с учебными пособиями, и я не могу понять, что заставляет его создавать новую строку, потому что я установил отношения к сущности Product- и ProductDetail (OneToOne) в слое Controller перед использованием. Кто-то спасет меня от этой борьбы, я застрял здесь более 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

@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...

ProductController

Метод получения формы для добавления нового товара (запрос 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";
    }

Способ обработки сохранения / обновления Продукта и его ProductDetail (запрос 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);
    }

Способ получения товара по его идентификатору

@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 вместо обновления ProductDetail в извлеченной сущности Product.

Ответы [ 2 ]

0 голосов
/ 05 мая 2019

Вам необходимо добавить скрытый ключ для productDetail.id, иначе он считает объект сведений о продукте временным и сохраняет его как новый объект вместо его обновления.

0 голосов
/ 05 мая 2019

Это потому, что вы используете примитивный тип данных int.Обновите его до Integer, и он должен работать нормально.

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

Обновите его до,

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

ПРИМЕЧАНИЕ. Восстановите соответствующий метод получения / установки для этого поля.

...