【问题标题】:Spring Boot - Transform response object/json - Flatten Array List of objects to DAOSpring Boot - 转换响应对象/json - 将对象的数组列表展平为 DAO
【发布时间】:2020-10-31 14:59:29
【问题描述】:

我正在创建一个库存管理系统。向内库存将是产品和相应数量的集合。因此,InwardInventory 和 InwardOutwardList 之间是一对多的。

public class InwardOutwardList extends ReusableFields
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Long entryid;
    
    @ManyToOne(fetch=FetchType.LAZY,cascade = CascadeType.ALL)
    @JoinColumn(name="productId",nullable=false)
    @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
    Product product;

    Double quantity;
//getter setter
}

进货库存

public class InwardInventory extends ReusableFields implements Cloneable
{

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="inwardid")
    Long inwardid;
    @NonNull
    String vehicleNo;
    @ManyToMany(fetch=FetchType.EAGER,cascade = CascadeType.ALL)
    @JoinTable(name = "inwardinventory_entry", joinColumns = {
            @JoinColumn(name = "inwardid", referencedColumnName = "inwardid") }, inverseJoinColumns = {
                    @JoinColumn(name = "entryId", referencedColumnName = "entryId") })
    Set<InwardOutwardList> inwardOutwardList = new HashSet<>();;
    

    @ManyToOne(fetch=FetchType.LAZY,cascade = CascadeType.ALL)
    @JoinColumn(name="warehouse_id",nullable=false)
    @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
    Warehouse warehouse;

因此,当我获取包含两个产品 iron 和水泥的单个内向库存时,我会收到如下嵌套 json 格式的数据

[{
  "inwardid": 19497,
  "vehicleNo": "TRUCK",
  "inwardOutwardList": [
    {
      "entryid": 19499,
      "product": {
        "productName": "Cement"
      },
      "quantity": 100
    },
    {
      "entryid": 19500,
      "product": {
        "productName": "Iron"
      },
      "quantity": 10
    }
  ],
  "warehouse": {
    "warehouseName": "war2"
  },
  "supplier": {
    "name": "Bright Traders"
  }
}]

现在,我想将其导出为 csv。所以,我需要把它展平,只有一个像下面这样的级别

[
  {
    "inwardid": 19497,
    "vehicleNo": "TRUCK",
    "productName": "Cement",
    "quantity": 100,
    "warehouseName": "war2"
  },
  {
    "inwardid": 19497,
    "vehicleNo": "TRUCK",
    "productName": "Iron",
    "quantity": 10,
    "warehouseName": "war2"
  }
]

我编写了简单的嵌套 for-each 迭代,它正在发挥作用

private List<InwardInventoryExportFullDAO> transformStructure(List<InwardInventory> iiDataList) 
    {
        List<InwardInventoryExportFullDAO> data = new ArrayList<InwardInventoryExportFullDAO>();
        for(InwardInventory iiData:iiDataList)
        {
            for(InwardOutwardList ioList:iiData.getInwardOutwardList())
            {
                InwardInventoryExportFullDAO tempExportData = new InwardInventoryExportFullDAO();
                tempExportData.setWarehouse(iiData.getWarehouse().getWarehouseName());
                tempExportData.setVehicleNo(iiData.getVehicleNo());
                tempExportData.setQuantity(ioList.getQuantity());
                tempExportData.setProductName(ioList.getProduct().getProductName());
                data.add(tempExportData);
            }
        }
        return data;
    }

但我认为如果内向库存记录数量激增,这不是最佳解决方案。我看到的另一个选项是使用并行流或 json 映射器。但我不确定哪个是实现这一目标的最佳和最佳解决方案。预计要导出 excel 的记录数在 10k 左右。

我已经编写的嵌套循环需要大约 5 秒来处理 2k 条记录。因此,将来负载增加时可能需要 30 多秒。所以,寻找最佳的最优解。

提前谢谢你

【问题讨论】:

  • 对于 CSV,您使用的是什么库?
  • 计划将此作为 Json 发送到前端。前端将进行导出
  • 要展平嵌套的 Json,您可以编写自定义反序列化器并将所有 Json 字段映射到平面对象。
  • @KevKosDev。我试着这样做。但我面临的问题是一对多映射。我需要来自父对象的 2 个字段和来自子对象列表的两个字段。父母的字段应该为每个孩子重复。所以,这是一种父子之间的交叉连接。我无法弄清楚如何实现这一目标。
  • 这是个坏主意,因为前端需要再次处理,而且我认为您当前的解决方案效率很高,可以进行一些小的修改,例如使用构造函数/构建器进行 DTO 和 2k 记录 5 秒也许还有别的需要时间。对于 10k 数据,处理它最多需要 1 秒。

标签: spring-boot spring-mvc jpa spring-data-jpa java-stream


【解决方案1】:

您可以编写自定义 JsonDeserializer 并将嵌套的 Json 字段提取到平面 pojo 中。看起来像这样:

public class Test extends JsonDeserializer<List<InventoryItem>> {

   private final ObjectMapper mapper = new ObjectMapper();
   private final ObjectReader reader = mapper.readerFor(new TypeReference<List<InventoryItem>>() {});

    @Override
    public List<InventoryItem> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {

        JsonNode node = p.getCodec().readTree(p);
        String inwardId = node.get("inwardId").textValue();

        List<InventoryItem> foo = reader.readValue(node.get("inwardOutwardList"));

        foo.forEach(o -> o.setInwardId(inwardId));

        return foo;
    }
}

首先从根对象中解压 id,然后反序列化项目并为每个项目设置根 id。

【讨论】:

  • 我创建了您提到的客户序列化程序。并为 InwardInventory @JsonDeserialize(using = InwardExportDeSerializer.class) public class InwardInventory 添加了注释,但是在执行时。未调用此序列化程序。知道为什么吗?
【解决方案2】:

通过添加自定义序列化程序解决了这个问题

@Component
public class InwardExportSerializer extends StdSerializer<InwardInventory> 
{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    protected InwardExportSerializer(Class<InwardInventory> t) 
    {
        super(t);
    }
    protected InwardExportSerializer() 
    {
        this(null);
    }
    @Override
    public void serialize(InwardInventory value, JsonGenerator gen, SerializerProvider provider) throws IOException 
    {
        for(InwardOutwardList ioList:value.getInwardOutwardList())
        {
            gen.writeStartObject();
            gen.writeNumberField("inwardid", value.getInwardid());
            gen.writeStringField("productname",ioList.getProduct().getProductName() );
            gen.writeEndObject();
        }
        
    }
        
}

【讨论】:

  • 然后你得到列表列表并在单个列表中再次转换它对吗?性能提高了吗?
  • 实际上不是。我测试了 3k 条记录,性能几乎相同。稍后将使用更大的数据集再次检查以检查对性能的确切影响。
  • 我希望这会是一样的!现在,您正在使用添加额外字段的代码处理更多数据。
  • @Eklavya 实际上是的。我发布这个答案只是因为如果有人有类似的问题,他们有一些东西需要审查。但就我而言,使用 for each 循环并遍历实体数据集并创建 DAO 似乎是更好的解决方案。在序列化程序的情况下,我正在控制响应 json 的创建,所以如果我必须更改字段的名称,我必须记住在序列化程序中也进行更改。所以,这是负担。此外,通过序列化程序并没有提高性能。因此,就像您在最初的评论中提到的那样,迭代实体列表似乎是更好的选择。
猜你喜欢
  • 2016-02-25
  • 2017-10-17
  • 2017-08-23
  • 2018-06-28
  • 2022-01-06
  • 2022-01-21
  • 1970-01-01
  • 2020-10-21
  • 2022-01-05
相关资源
最近更新 更多