繁体   English   中英

Spring JPA:如何在使用多对多关系时插入/更新具有唯一列的新项目

[英]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

在这个例子中,我没有看到多对多关系的必要性。 一对多可以达到目的,其中一个类别可以有多个产品,您可以使用名称唯一的同一类别创建多个产品。

如果你仍然想实现它,你需要有一个新表,其中包含两个表的主键的组合,这将是唯一的组合。 请参阅这些页面以获取更多信息12的视觉表示。

尽量避免将CascadeType.REMOVE用于多对多关联,因为它可能会生成过多的查询并删除比您预期更多的记录。 是一篇详细解释这种行为的文章。 由于您在映射中使用CascadeType.ALL ,因此它还包括CascadeType.REMOVE

关于向产品实体添加/更新新类别。 覆盖equalshashCode方法会很有帮助。 由于在您的示例中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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM