Java JPA - 如何将对象放入列表并使用 CRUD 更新列表对象?

Java JPA - How PUT an object with a list and update the list objects as well with CRUD?

我有一个对象,其中包含我似乎无法正确更新的其他对象列表。我可以用对象列表(ProductItemQuantity)创建对象(Product)没问题。我也可以对对象列表执行 PUT,但它会创建一个新的对象列表,我执行 PUT 的所有操作。我希望更新我提供的对象列表,而不是让它在我每次放置父对象时都创建一个新列表。

如果我将 ID 添加到 ProductItemQuantity,我会遇到异常:

detached entity passed to persist

这是我的 类:

Product.java

    @Entity 
public class Product {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY) 
    private Long id;
    @ManyToOne
    private Organization org;
    private String barCode;
    private String name;
    @ManyToOne(cascade=CascadeType.MERGE)
    private Status status;
    @ManyToMany(cascade=CascadeType.MERGE)
    private List<Fee> fees;
    @ManyToMany(cascade=CascadeType.ALL)
    private List<Note> notes;
    @ManyToMany(cascade=CascadeType.ALL)
    private List<ProductItemQuantity> productItems;
    private Integer stock;
    private BigDecimal msrp;
    @CreationTimestamp
    private LocalDateTime createdOn;
    @UpdateTimestamp
    private LocalDateTime updatedOn;

    // Getter & Setters

ProductItemQuantity.java

@Entity 
public class ProductItemQuantity {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY) 
    private Long id;
    private Integer count;
    @ManyToOne
    private ProductItem productItem;
    @CreationTimestamp
    private LocalDateTime createdOn;
    @UpdateTimestamp
    private LocalDateTime updatedOn;

// Getters / setters

ProductItem.java

@Entity 
public class ProductItem {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY) 
    private Long id;
    @ManyToOne
    private Organization org;
    @ManyToOne
    private Supplier supplier;
    private String barCode;
    private String description;
    private String name;
    private Integer stock;
    private Integer caseQty;
    private BigDecimal caseCost;
    @ManyToMany(cascade=CascadeType.ALL)
    private List<Note> notes;
    @CreationTimestamp
    private LocalDateTime createdOn;
    @UpdateTimestamp
    private LocalDateTime updatedOn;

ProductController.java

@PutMapping("/{id}")
public Product update(@RequestBody Product product, @PathVariable long id) {


    Product savedProduct = productService.save(product);


    return savedProduct;
}

工作 CRUD PUT 请求:http://localhost:8080/product/1

{
    "barcode":"12347163",
    "name":"Product 1",
    "stock": 12,
    "msrp": 29.99,
    "org": {
        "id":1
    },
    "status":{
        "id":1
    },
    "productItems":[{
        "count":30
    },{
        "count":30
    }
        ],
        "fees":[{
            "id":1

        },{
            "id":2

        }],
    "notes":[{
        "title":"Product Created",
        "description":"Note created by user X on 12/16/2019 11:00PM"
    },{
        "title":"Product Updated",
        "description":"Product updated stock by user X on 12/16/2019 11:00PM"
    }]
}

损坏的 CRUD PUT 请求:http://localhost:8080/product/1

{
    "barcode":"12347163",
    "name":"Product 1",
    "stock": 12,
    "msrp": 29.99,
    "org": {
        "id":1
    },
    "status":{
        "id":1
    },
    "productItems":[{
        "id":1,
        "count":30
    },{
        "id":2,
        "count":30
    }
        ],
        "fees":[{
            "id":1

        },{
            "id":2

        }],
    "notes":[{
        "title":"Product Created",
        "description":"Note created by user X on 12/16/2019 11:00PM"
    },{
        "title":"Product Updated",
        "description":"Product updated stock by user X on 12/16/2019 11:00PM"
    }]
}

您的对象已分离,因为关系(OneToMany、ManyToMany)仅从一个方向设置。为了持久化它们,您必须设置双向关系。您的关系是单向的,因为解析器 (jackson) 将生成以下对象:

Product product = new Product();
Fee fee = new Fee();
fee.setId(1);
product.setFees(Arrays.asList(fee));

在双向关系中,双方都必须设置:

product.getFees().forEach(fee-> fee.getProducts().add(product));

将持久对象与控制器对象分开是一个很好的做法,因为持久对象也处理关系。

根据我的经验,如果您想使用 GeneratedValue,您必须先 select 从数据库中获取实体,然后再对其进行修改。如果您希望 hibernate 生成它,那么创建一个新对象并为其设置 id 没有多大意义。

所以,您可能需要先做一个 select :

List fees = // select 产品费用列表中带有id的所有费用

及之后:

product.setFees(fees);
fees.forEach(fee -> fee.getProducts().add(product));

你的方法是一个 PUT,所以你不应该直接保存产品对象(它会在数据库中创建一个新条目)。

@PutMapping("/{id}")
public Product update(@RequestBody Product product, @PathVariable long id) {
    Optional<Product> originalProductOptional = productRepository.findById(id);
    // you should add a check here : if originalProduct is not found, return 404
    Product originalProduct = originalProductOptional.get();
    originalProduct.setName(product.getName());
    // here update all fields and relations

    productRepository.save(originalProduct);
    return originalProduct;
 }

答案是将 属性 更新为:

@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 
private List<ProductItemQuantity> productItemQuantities;

然后在put里手动设置

    List<ProductItemQuantity> piqs = product.getProductItemQuantities();

    if (piqs != null) {
        List<ProductItemQuantity> piiList = new ArrayList<ProductItemQuantity>();
        for (ProductItemQuantity pii : piqs) {
            // Check for ID in here?
            ProductItemQuantity curPII = piqService.getOne(pii.getId());
            curPII.setCount(pii.getCount());
            piiList.add(curPII);
        }
        originalProduct.setProductItemQuantities(piiList);
    }