В настоящее время я создаю веб-сайт для управления продуктами для зарегистрированного пользователя. Он использует 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.