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);
}
我有一个对象,其中包含我似乎无法正确更新的其他对象列表。我可以用对象列表(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);
}