Spring Jackson 通过 Id 引用现有对象反序列化对象
Spring Jackson deserialize object with reference to existing object by Id
我有以下三个实体 类:
发货实体:
@Entity
@Table(name = "SHIPMENT")
public class Shipment implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "SHIPMENT_ID", nullable = false)
private int shipmentId;
@Column(name = "DESTINATION", nullable = false)
private String destination;
@OneToMany(mappedBy = "shipment")
private List<ShipmentDetail> shipmentDetailList;
//bunch of other variables omitted
public Shipment(String destination) {
this.destination = destination;
shipmentDetailList = new ArrayList<>();
}
货件详细信息实体:
@Entity
@Table(name = "SHIPMENT_DETAIL")
public class ShipmentDetail implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "SHIPMENT_DETAIL_ID", nullable = false)
private int shipmentDetailId;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID", nullable = false)
private Product product;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "SHIPMENT_ID", nullable = false)
private Shipment shipment;
//bunch of other variables omitted
public ShipmentDetail() {
}
public ShipmentDetail(Shipment shipment, Product product) {
this.product = product;
this.shipment = shipment;
}
产品实体:
@Entity
@Table(name = "Product")
public class Product implements Serializable {
@Id
@Column(name = "PRODUCT_ID", nullable = false)
private String productId;
@Column(name = "PRODUCT_NAME", nullable = false)
private String productName;
//bunch of other variables omitted
public Product() {
}
public Product(String productId, String productName) {
this.productId = productId;
this.productName = productName;
}
我通过休息 API 接收 JSONs。问题是我不知道如何反序列化带有 shipmentDetails 的新 Shipment,这些 ShipmentDetails 仅通过 ID 与现有对象有关系。我知道您可以简单地使用 objectmapper 反序列化,但这需要产品的所有字段都在每个 shipmentDetail 中。我如何仅使用 productID 进行实例化?
样本JSON收到
{
"destination": "sample Dest",
"shipmentDetails": [
{
"productId": "F111111111111111"
},
{
"productId": "F222222222222222"
}
]
}
目前我的休息端点将收到 JSON,然后执行此操作:
public ResponseEntity<String> test(@RequestBody String jsonString) throws JsonProcessingException {
JsonNode node = objectMapper.readTree(jsonString);
String destination = node.get("destination").asText();
Shipment newShipment = new Shipment(destination);
shipmentRepository.save(newShipment);
JsonNode shipmentDetailsArray = node.get("shipmentDetails");
int shipmentDetailsArrayLength = shipmentDetailsArray.size();
for (int c = 0; c < shipmentDetailsArrayLength; c++) {
String productId = node.get("productId").asText();
Product product = productRepository.findById(productId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No product with ID of: " + productId + " exists!"));
ShipmentDetail shipmentDetail = new ShipmentDetail(newShipment, product, quantity);
shipmentDetailRepository.save(shipmentDetail);
}
}
我想做的是:
public ResponseEntity<String> test2(@RequestBody String jsonString) throws JsonProcessingException {
JsonNode wholeJson = objectMapper.readTree(jsonString);
Shipment newShipment = objectMapper.treeToValue(wholeJson, Shipment.class);
return new ResponseEntity<>("Transfer Shipment successfully created", HttpStatus.OK);
}
我试过这个解决方案没有。效用:
如何让产品实体搜索现有产品而不是尝试创建新产品。我一直使用的 hacky 极其低效的解决方法是遍历 json 数组,并为每个 productId 使用 productRepository 查找产品,然后将 shipmentDetail 与产品一一设置。我不确定这是否是我自学的最佳实践 spring。
所以在伪代码中我试图做的是:
- 收到JSON
- 实例化 Shipment 实体
- 实例化一组 shipmentDetail 实体
对于每批货物详细信息:
1. 查找具有给定 productId 的产品
2. 用产品和装运实例化 shipmentDetail
代码已显着简化以更好地展示问题,
我认为你目前的方法不是一个糟糕的解决方案,你正在正确地处理问题并且只需要几个步骤。
无论如何,您可以尝试以下方法。
我们的想法是提供一个新字段 productId
,定义在支持与 Product
实体关系的同一数据库列上,例如:
@Entity
@Table(name = "SHIPMENT_DETAIL")
public class ShipmentDetail implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "SHIPMENT_DETAIL_ID", nullable = false)
private int shipmentDetailId;
@Column(name = "PRODUCT_ID", nullable = false)
private String productId;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID", insertable = false, updatable = false)
private Product product;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "SHIPMENT_ID", nullable = false)
private Shipment shipment;
//bunch of other variables omitted
public ShipmentDetail() {
}
public ShipmentDetail(Shipment shipment, Product product) {
this.product = product;
this.shipment = shipment;
}
}
product
字段必须注解为not insertable
而不是updatable
:相反,Hibernate会抱怨应该使用哪个字段来维护与Product
实体,换句话说,保持实际的列值。
修改 Shipment
与 ShipmentDetail
的关系以传播持久性操作(根据您的需要调整代码):
@OneToMany(mappedBy = "shipment", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ShipmentDetail> shipmentDetailList;
然后,您可以安全地依赖 Spring+Jackson 反序列化并获得对接收到的 Shipment
对象的引用:
public ResponseEntity<String> processShipment(@RequestBody Shipment shipment) {
// At this point shipment should contain the different details,
// each with the corresponding productId information
// Perform the validations required, log information, if necessary
// Save the object: it should persist the whole object tree in the database
shipmentRepository.save(shipment);
}
这种方法有一个明显的缺点,Product
的存在没有事先检查。
虽然您可以使用外键确保数据库级别的数据完整性,但在执行实际插入之前验证信息是否正确可能会很方便:
public ResponseEntity<String> processShipment(@RequestBody Shipment shipment) {
// At this point shipment should contain the different details,
// each with the corresponding productId information
// Perform the validations required, log information, if necessary
List<ShipmentDetail> shipmentDetails = shipment.getShipmentDetails();
if (shipmentDetails == null || shipmentDetails.isEmpty()) {
// handle error as appropriate
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "No shipment details provided");
}
shipmentDetails.forEach(shipmentDetail -> {
String productId = shipmentDetail.getProductId();
Product product = productRepository.findById(productId).orElseThrow(
() -> new ResponseStatusException(HttpStatus.NOT_FOUND,
"No product with ID of: " + productId + " exists!")
)
});
// Everything looks fine, save the object now
shipmentRepository.save(shipment);
}
你这部分的代码有瓶颈:
Product product = productRepository.findById(productId)
因为您正在为每个 productId 进行查询,并且它会在大量产品时表现不佳。忽略这一点,我会推荐这种方法。
构建您自己的解串器(参见this):
public class ShipmentDeserializer extends JsonDeserializer {
@Override
public Shipment deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String destination = node.get("destination").asText();
Shipment shipment = new Shipment(destination);
JsonNode shipmentDetailsNode = node.get("shipmentDetails");
List shipmentDetailList = new ArrayList();
for (int c = 0; c < shipmentDetailsNode.size(); c++) {
JsonNode productNode = shipmentDetailsNode.get(c);
String productId = productNode.get("productId").asText();
Product product = new Product(productId);
ShipmentDetail shipmentDetail = new ShipmentDetail(product);
shipmentDetailList.add(shipmentDetail);
}
shipment.setShipmentDetailList(shipmentDetailList);
return shipment;
}
}
将解串器添加到您的 Shipment class:
@JsonDeserialize(using = ShipmentDeserializer .class)
public class Shipment {
// Class code
}
反序列化字符串:
public ResponseEntity test2(@RequestBody String jsonString) throws JsonProcessingException {
Shipment newShipment = objectMapper.readValue(jsonString, Shipment.class);
/* More code */
return new ResponseEntity("Transfer Shipment successfully created", HttpStatus.OK);
}
此时,您只是将Json转换为class,所以我们需要持久化数据。
public ResponseEntity test2(@RequestBody String jsonString) throws JsonProcessingException {
Shipment newShipment = objectMapper.readValue(jsonString, Shipment.class);
shipmentRepository.save(newShipment);
List<ShipmentDetail> shipmentDetails = newShipment.getShipmentDetailList();
for (int i = 0; i < shipmentDetails.size(); c++) {
ShipmentDetail shipmentDetail = shipmentDetails.get(i);
shipmentDetail.setShipment(newShipment);
Product product = productRepository.findById(productId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No product with ID of: " + productId + " exists!"));
shipmentDetail.setProduct(product);
shipmentDetailRepository.save(shipmentDetail);
}
return new ResponseEntity("Transfer Shipment successfully created", HttpStatus.OK);
}
我知道您想减少测试方法中的代码,但是我不建议将Json反序列化与持久层结合起来。但是,如果您想遵循该路径,则可以将 productRepository.findById(productId) 移动到 ShipmentDeserializer class 中,如下所示:
public class ShipmentDeserializer extends JsonDeserializer {
@Override
public Shipment deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String destination = node.get("destination").asText();
Shipment shipment = new Shipment(destination);
JsonNode shipmentDetailsNode = node.get("shipmentDetails");
List shipmentDetailList = new ArrayList();
for (int c = 0; c < shipmentDetailsNode.size(); c++) {
JsonNode productNode = shipmentDetailsNode.get(c);
String productId = productNode.get("productId").asText();
Product product = productRepository.findById(productId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No product with ID of: " + productId + " exists!"));
ShipmentDetail shipmentDetail = new ShipmentDetail(product);
shipmentDetailList.add(shipmentDetail);
}
shipment.setShipmentDetailList(shipmentDetailList);
return shipment;
}
}
但是如果你想这样做,你需要将存储库注入反序列化器(参见 this)。
我有以下三个实体 类:
发货实体:
@Entity
@Table(name = "SHIPMENT")
public class Shipment implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "SHIPMENT_ID", nullable = false)
private int shipmentId;
@Column(name = "DESTINATION", nullable = false)
private String destination;
@OneToMany(mappedBy = "shipment")
private List<ShipmentDetail> shipmentDetailList;
//bunch of other variables omitted
public Shipment(String destination) {
this.destination = destination;
shipmentDetailList = new ArrayList<>();
}
货件详细信息实体:
@Entity
@Table(name = "SHIPMENT_DETAIL")
public class ShipmentDetail implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "SHIPMENT_DETAIL_ID", nullable = false)
private int shipmentDetailId;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID", nullable = false)
private Product product;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "SHIPMENT_ID", nullable = false)
private Shipment shipment;
//bunch of other variables omitted
public ShipmentDetail() {
}
public ShipmentDetail(Shipment shipment, Product product) {
this.product = product;
this.shipment = shipment;
}
产品实体:
@Entity
@Table(name = "Product")
public class Product implements Serializable {
@Id
@Column(name = "PRODUCT_ID", nullable = false)
private String productId;
@Column(name = "PRODUCT_NAME", nullable = false)
private String productName;
//bunch of other variables omitted
public Product() {
}
public Product(String productId, String productName) {
this.productId = productId;
this.productName = productName;
}
我通过休息 API 接收 JSONs。问题是我不知道如何反序列化带有 shipmentDetails 的新 Shipment,这些 ShipmentDetails 仅通过 ID 与现有对象有关系。我知道您可以简单地使用 objectmapper 反序列化,但这需要产品的所有字段都在每个 shipmentDetail 中。我如何仅使用 productID 进行实例化?
样本JSON收到
{
"destination": "sample Dest",
"shipmentDetails": [
{
"productId": "F111111111111111"
},
{
"productId": "F222222222222222"
}
]
}
目前我的休息端点将收到 JSON,然后执行此操作:
public ResponseEntity<String> test(@RequestBody String jsonString) throws JsonProcessingException {
JsonNode node = objectMapper.readTree(jsonString);
String destination = node.get("destination").asText();
Shipment newShipment = new Shipment(destination);
shipmentRepository.save(newShipment);
JsonNode shipmentDetailsArray = node.get("shipmentDetails");
int shipmentDetailsArrayLength = shipmentDetailsArray.size();
for (int c = 0; c < shipmentDetailsArrayLength; c++) {
String productId = node.get("productId").asText();
Product product = productRepository.findById(productId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No product with ID of: " + productId + " exists!"));
ShipmentDetail shipmentDetail = new ShipmentDetail(newShipment, product, quantity);
shipmentDetailRepository.save(shipmentDetail);
}
}
我想做的是:
public ResponseEntity<String> test2(@RequestBody String jsonString) throws JsonProcessingException {
JsonNode wholeJson = objectMapper.readTree(jsonString);
Shipment newShipment = objectMapper.treeToValue(wholeJson, Shipment.class);
return new ResponseEntity<>("Transfer Shipment successfully created", HttpStatus.OK);
}
我试过这个解决方案没有。效用:
如何让产品实体搜索现有产品而不是尝试创建新产品。我一直使用的 hacky 极其低效的解决方法是遍历 json 数组,并为每个 productId 使用 productRepository 查找产品,然后将 shipmentDetail 与产品一一设置。我不确定这是否是我自学的最佳实践 spring。
所以在伪代码中我试图做的是:
- 收到JSON
- 实例化 Shipment 实体
- 实例化一组 shipmentDetail 实体 对于每批货物详细信息: 1. 查找具有给定 productId 的产品 2. 用产品和装运实例化 shipmentDetail
代码已显着简化以更好地展示问题,
我认为你目前的方法不是一个糟糕的解决方案,你正在正确地处理问题并且只需要几个步骤。
无论如何,您可以尝试以下方法。
我们的想法是提供一个新字段 productId
,定义在支持与 Product
实体关系的同一数据库列上,例如:
@Entity
@Table(name = "SHIPMENT_DETAIL")
public class ShipmentDetail implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "SHIPMENT_DETAIL_ID", nullable = false)
private int shipmentDetailId;
@Column(name = "PRODUCT_ID", nullable = false)
private String productId;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID", insertable = false, updatable = false)
private Product product;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "SHIPMENT_ID", nullable = false)
private Shipment shipment;
//bunch of other variables omitted
public ShipmentDetail() {
}
public ShipmentDetail(Shipment shipment, Product product) {
this.product = product;
this.shipment = shipment;
}
}
product
字段必须注解为not insertable
而不是updatable
:相反,Hibernate会抱怨应该使用哪个字段来维护与Product
实体,换句话说,保持实际的列值。
修改 Shipment
与 ShipmentDetail
的关系以传播持久性操作(根据您的需要调整代码):
@OneToMany(mappedBy = "shipment", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ShipmentDetail> shipmentDetailList;
然后,您可以安全地依赖 Spring+Jackson 反序列化并获得对接收到的 Shipment
对象的引用:
public ResponseEntity<String> processShipment(@RequestBody Shipment shipment) {
// At this point shipment should contain the different details,
// each with the corresponding productId information
// Perform the validations required, log information, if necessary
// Save the object: it should persist the whole object tree in the database
shipmentRepository.save(shipment);
}
这种方法有一个明显的缺点,Product
的存在没有事先检查。
虽然您可以使用外键确保数据库级别的数据完整性,但在执行实际插入之前验证信息是否正确可能会很方便:
public ResponseEntity<String> processShipment(@RequestBody Shipment shipment) {
// At this point shipment should contain the different details,
// each with the corresponding productId information
// Perform the validations required, log information, if necessary
List<ShipmentDetail> shipmentDetails = shipment.getShipmentDetails();
if (shipmentDetails == null || shipmentDetails.isEmpty()) {
// handle error as appropriate
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "No shipment details provided");
}
shipmentDetails.forEach(shipmentDetail -> {
String productId = shipmentDetail.getProductId();
Product product = productRepository.findById(productId).orElseThrow(
() -> new ResponseStatusException(HttpStatus.NOT_FOUND,
"No product with ID of: " + productId + " exists!")
)
});
// Everything looks fine, save the object now
shipmentRepository.save(shipment);
}
你这部分的代码有瓶颈:
Product product = productRepository.findById(productId)
因为您正在为每个 productId 进行查询,并且它会在大量产品时表现不佳。忽略这一点,我会推荐这种方法。
构建您自己的解串器(参见this):
public class ShipmentDeserializer extends JsonDeserializer { @Override public Shipment deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonNode node = jp.getCodec().readTree(jp); String destination = node.get("destination").asText(); Shipment shipment = new Shipment(destination); JsonNode shipmentDetailsNode = node.get("shipmentDetails"); List shipmentDetailList = new ArrayList(); for (int c = 0; c < shipmentDetailsNode.size(); c++) { JsonNode productNode = shipmentDetailsNode.get(c); String productId = productNode.get("productId").asText(); Product product = new Product(productId); ShipmentDetail shipmentDetail = new ShipmentDetail(product); shipmentDetailList.add(shipmentDetail); } shipment.setShipmentDetailList(shipmentDetailList); return shipment; } }
将解串器添加到您的 Shipment class:
@JsonDeserialize(using = ShipmentDeserializer .class) public class Shipment { // Class code }
反序列化字符串:
public ResponseEntity test2(@RequestBody String jsonString) throws JsonProcessingException { Shipment newShipment = objectMapper.readValue(jsonString, Shipment.class); /* More code */ return new ResponseEntity("Transfer Shipment successfully created", HttpStatus.OK); }
此时,您只是将Json转换为class,所以我们需要持久化数据。
public ResponseEntity test2(@RequestBody String jsonString) throws JsonProcessingException { Shipment newShipment = objectMapper.readValue(jsonString, Shipment.class); shipmentRepository.save(newShipment); List<ShipmentDetail> shipmentDetails = newShipment.getShipmentDetailList(); for (int i = 0; i < shipmentDetails.size(); c++) { ShipmentDetail shipmentDetail = shipmentDetails.get(i); shipmentDetail.setShipment(newShipment); Product product = productRepository.findById(productId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No product with ID of: " + productId + " exists!")); shipmentDetail.setProduct(product); shipmentDetailRepository.save(shipmentDetail); } return new ResponseEntity("Transfer Shipment successfully created", HttpStatus.OK); }
我知道您想减少测试方法中的代码,但是我不建议将Json反序列化与持久层结合起来。但是,如果您想遵循该路径,则可以将 productRepository.findById(productId) 移动到 ShipmentDeserializer class 中,如下所示:
public class ShipmentDeserializer extends JsonDeserializer {
@Override
public Shipment deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
String destination = node.get("destination").asText();
Shipment shipment = new Shipment(destination);
JsonNode shipmentDetailsNode = node.get("shipmentDetails");
List shipmentDetailList = new ArrayList();
for (int c = 0; c < shipmentDetailsNode.size(); c++) {
JsonNode productNode = shipmentDetailsNode.get(c);
String productId = productNode.get("productId").asText();
Product product = productRepository.findById(productId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "No product with ID of: " + productId + " exists!"));
ShipmentDetail shipmentDetail = new ShipmentDetail(product);
shipmentDetailList.add(shipmentDetail);
}
shipment.setShipmentDetailList(shipmentDetailList);
return shipment;
}
}
但是如果你想这样做,你需要将存储库注入反序列化器(参见 this)。