[英]Spring JPA: How to insert/update new item with unique column when using Many to Many relationship
我想创建一个带有类别的新产品。 如果该类别不存在,则还要创建该类别。 到目前为止,这个用例很好。
我的第二个用例是当我想创建另一个具有相同类别的产品时。 由于我的 Category 实体具有唯一名称,因此当我调用我的服务时,它会抛出重复的错误键。 我想要的是能够创建第二个产品并更新类别以拥有该产品。
这是我的类别实体
package com.smolano.cupboard.entities;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Category implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String name;
@ManyToMany(mappedBy = "categories", fetch = FetchType.LAZY)
private Set<Product> products = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Product> getProducts() {
return products;
}
public void setProducts(Set<Product> products) {
this.products = products;
}
}
这是我的产品实体
package com.smolano.cupboard.entities;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Product implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(
name = "product_categories",
joinColumns = {@JoinColumn(name = "product_id")},
inverseJoinColumns = {@JoinColumn(name = "category_id")}
)
private Set<Category> categories = new HashSet<>();
private String barCode;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Category> getCategories() {
return categories;
}
public void setCategories(Set<Category> categories) {
this.categories = categories;
}
public String getBarCode() {
return barCode;
}
public void setBarCode(String barCode) {
this.barCode = barCode;
}
}
我的类别存储库
package com.smolano.cupboard.repositories;
import com.smolano.cupboard.entities.Category;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CategoryRepository extends JpaRepository<Category, Long> {
}
和我的产品存储库
package com.smolano.cupboard.repositories;
import com.smolano.cupboard.entities.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
我的其余代码可以在这里找到https://github.com/santfirax/backend-product-store-java
尽量避免将CascadeType.REMOVE
用于多对多关联,因为它可能会生成过多的查询并删除比您预期更多的记录。 这是一篇详细解释这种行为的文章。 由于您在映射中使用CascadeType.ALL
,因此它还包括CascadeType.REMOVE
。
关于向产品实体添加/更新新类别。 覆盖equals
和hashCode
方法会很有帮助。 由于在您的示例中Category.name
应该是唯一的,并且基本上您可以通过名称区分类别,因此您的实现可能如下所示:
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Category))
return false;
Category other = (Category) o;
return (this.name == null && other.name == null) || (this.name != null && this.name.equals(other.name));
}
@Override
public int hashCode() {
return this.name.hashCode();
}
这样您就可以确保您的Product.categories
字段不会包含同名的类别。
假设您的实体映射具有cascade = {CascadeType.PERSIST, CascadeType.MERGE}
,您的服务类可能类似于以下内容:
@Service
@Transactional
public class ProductService {
@Autowired
private ProductRepository productRepository;
public Product createProduct(Product product) {
return productRepository.save(product);
}
public Product saveCategoryInProduct(Long productId, Category category) {
return productRepository
.findById(productId)
.map(product -> addCategory(product, category))
.map(productRepository::save)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
private Product addCategory(Product product, Category category) {
product.getCategories().add(category);
return product;
}
}
通过调用saveCategoryInProduct
,您将向产品实体添加新类别。 如果 Category 实体包含id
(非空),那么将在product_categories
表中创建新记录。
但是,如果Category
实体没有id
(为 null),那么将首先尝试在category
表中创建新记录,然后将其关联到提供的产品id
(通过在product_categories
创建记录)。 在这种情况下,您可能会遇到DuplicateKeyException
。 要处理它,您可以通过以下方式修改addCategory
方法:
private Product addCategory(Product product, Category category) {
product.getCategories().add(
categoryRepository
.findByName(category)
.orElse(category));
return product;
}
通过这种方式,您将确保您添加的类别是从 db 中检索到的(因此不会再次创建)或尚不存在。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.