来自 SQL 视图的嵌套 JSON 响应

Posted

技术标签:

【中文标题】来自 SQL 视图的嵌套 JSON 响应【英文标题】:Nested JSON response from SQL View 【发布时间】:2021-05-31 01:46:19 【问题描述】:

我有一个 SQL 视图 (IdView),它具有以下值。我对此视图只有读取权限,对基础表没有任何访问权限。

+-------+-------+------------+------------+---------+-----------+----------------+
| ID    | Name  | Desc       | Relation   | ChildId | ChildName | ChildDesc      |
+-------+-------+------------+------------+---------+-----------+----------------+
| 80121 | Car   | Model A    | Kits       | 50123   | Bolt      | Hexagonal Bolt |
| 80121 | Car   | Model A    | Kits       | 50124   | Nut       | 25mm Dia       |
| 80121 | Car   | Model A    | Spare      | 50125   | screw     | Type A         |
| 80121 | Car   | Model A    | Spare      | 50126   | Shaft     | 10m long       |
| 80122 | Bike  | Model H    | Spare      | 50127   | Oil       | Standard oil   |
+-------+-------+------------+------------+---------+-----------+----------------+

现在我必须在用户点击以下 URL 时提供以下响应,即对于 id 80121 http://localhost:8080/items?id=80121 。将有 2 个关系:KitsSpare。我想将所有套件保存在一个键中,并且类似地备用在另一个键中,如下所示。


    "Id": "80121",
    "Name": "Car",
    "Desc": "Model A",
    "Kits": [
        
            "Id": "50123",
            "Name": "Bolt",
            "Desc": "Hexagonal Bolt"
        ,
        
            "Id": "50124",
            "Name": "Nut",
            "Desc": "25mm Dia"
        ,
    ],
    "Spare": [
        
            "Id": "50125",
            "Name": "screw",
            "Desc": "Type A"
        ,
        
            "Id": "50126",
            "Name": "Shaft",
            "Desc": "10m long"
        ,
    ]

同样当用户点击 http://localhost:8080/items?id=80112


    "Id": "80112",
    "Name": "Bike",
    "Desc": "Model H",
    "Kits": [],
    "Spare": [
        
            "Id": "50127",
            "Name": "Oil",
            "Desc": "Standard oil"
        
    ]

每个请求只有一个 id。请帮助实现这一目标

我在下面试过了。

存储库

@Repository
public interface MyDataRepo extends JpaRepository<List, String> 

    @Query(value="select ID,Name,Desc,Relation,ChildId,ChildName,ChildDesc from myview
                  WHERE ID=?1",nativeQuery=true)
    List<Data> findAllCategory(String id);

    public static interface Data 
      String getid();
      String getname();
      String getdesc();
      String getrelation();
      String getchildid();
      String getchildname();
      String getchilddesc();
    

服务:

public List<Data> getMyData(String id) 
    return repo.findAllCategory(id);

控制器:

@GetMapping("/items")
public ResponseEntity<List<Data>> retrieveData(@RequestParam("id") String id) 
    List<Data> stud = service.getMyData(id);
    return ResponseEntity.ok().body(stud);

80121 的电流输出:

[
    "Id": "80121",
    "Name": "Car",
    "Desc": "Model A",
    "Relation":"Kits",
    "ChildId":"50123",
    "ChildName":"Bolt",
    "ChildDesc":"Hexagonal Bolt"
    
, 
    "Id": "80121",
    "Name": "Car",
    "Desc": "Model A",
    "Relation":"Kits",
    "ChildId":"50124",
    "ChildName":"Nut",
    "ChildDesc":"25mm Dia"
, 
    "Id": "80121",
    "Name": "Car",
    "Desc": "Model A",
    "Relation":"Spare",
    "ChildId":"50125",
    "ChildName":"screw",
    "ChildDesc":"10m long "
, 
    "Id": "80121",
    "Name": "Car",
    "Desc": "Model A",
    "Relation":"Spare",
    "ChildId":"50126",
    "ChildName":"Shaft",
    "ChildDesc":"Pasted Seal"
]

我是 Java 和 Spring Boot 的初学者。我应该使用视图列创建自定义 POJO 或实体吗?我不知道如何创建这个嵌套的 JSON 并继续进行。任何方向将不胜感激。

【问题讨论】:

【参考方案1】:

从数据库加载数据后,您需要对其进行处理。最简单的方法是按Relation 列分组并将对象映射到Map 实例。您可以将以下方法添加到您的服务层并在您的控制器中调用:

public Map<String, Object> getGroupedByRelationData(String id) 
    List<Data> data = repo.findAllCategory(id)
    if (data == null || data.isEmpty()) 
        return Collections.emptyMap();
    

    // from first objet on the list copy common properties
    Data first = data.get(0);
    Map<String, Object> result = new LinkedHashMap<>();
    result.put("Id", first.getId());
    result.put("Name", first.getName());
    result.put("Desc", first.getDesc());
    
    // group data by relation field
    Map<String, List<Data>> grouped = data.stream().collect(Collectors.groupingBy(Data::getRelation));
    
    // each entity convert to map with child values
    grouped.forEach((k, v) -> 
        result.put(k, v.stream().map(inputData -> 
            Map<String, Object> childResult = new HashMap<>();
            childResult.put("Id", inputData.getChildId());
            childResult.put("Name", inputData.getChildName());
            childResult.put("Desc", inputData.getChildDesc());

            return childResult;
        ).collect(Collectors.toList()));
    );

    return result;

当然,你需要更新控制器方法返回的类型。

【讨论】:

感谢 loooooottt @Michal。你救了我的一天! :) :)【参考方案2】:

您这样做的方式是每行带来信息。解决它的一种方法是在返回控制之前开发一个序列化函数,例如 DTO。

另一种解决方案是正确映射实体并尝试执行相同的选择,这样它就可以按预期返回

【讨论】:

谢谢。如果您能提供一些示例代码,那就太好了。作为一个 SQL 人,我对创建 DTO、创建 Map 等 java 语法并不熟悉, 把你在 github 上的项目 url 发给我,我可以帮你到那里 很遗憾我没有在 Github 上创建任何项目。这也是项目特定的代码,我不允许在其他任何地方使用该代码。我在这里给出的问题是一个示例(修改版)【参考方案3】:

您应该声明三个单独的实体,如下所示。并在休息控制器中访问它们。 KitEntity

package com.example.demo;

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.persistence.*;

@Entity
public class KitEntity 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // automatic ID creation by hibernate.
    private int id;
    private String name;
    private String desc;

    @ManyToOne
    @JoinColumn(name = "car_entity") // foreign key column which points to car table
    @JsonIgnore 
    private CarEntity carEntity;
    
    // getters and setters here

备用实体

package com.example.demo;

import com.fasterxml.jackson.annotation.JsonIgnore;

import javax.persistence.*;

@Entity
public class SpareEntity 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    private String desc;
    @ManyToOne
    @JoinColumn(name = "car_entity") // foreign key column which points to car table
    @JsonIgnore
    private CarEntity carEntity;
    
    // setters and getters here

汽车实体

@Entity
public class CarEntity 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @OneToMany(mappedBy = "carEntity") // here carEntity is a veriable name in KitEntity who have ManyToOne relation.
    private List<KitEntity> kits;
    @OneToMany(mappedBy = "carEntity") // here carEntity is a veriable name in SpareEntity who have ManyToOne relation.
    private List<SpareEntity> spare;

    // getters and setters here.

您可以使用 Spring Data JPA 的 JpaRepository 进行数据库访问。或者您可以使用休眠查询从该实体获取数据。 此外,您需要两个单独的实体来维护两个不同类型的列表,即备用和套件。您也可以使用枚举来定义类型,但这会很复杂,所以我不会那样做。

附言另请注意,您必须在 kit 和备用实体上使用 @JsonIgnore 以确保不会发生对 getter 的递归调用。

编辑: car_entity 是您的套件和备用实体中的列名。 如果您想自己创建表,那么您应该在每个验证器上使用 @Column 注释将其映射到数据库中的适当列。以及类级别的@Table 并在该注释中提供您的表名。

【讨论】:

非常感谢。您能澄清一下name = "car_entity" 是什么吗?我没有这样的专栏。另外我应该如何在控制器中访问它们?我应该更改我的 SQL 查询吗?如前所述,我对 Java 很陌生,如果您也能包含它们,那将非常有帮助 您的数据库中将有 3 个表,这些表将映射到上述所有实体。这个 car_entity 是对汽车表的外键引用。您可以在数据库中手动创建表或让 hibernate 为您完成工作。它将基于实体和注释创建新表。(为了清楚起见,您也可以调用 car_entity, car_id) 再次感谢。但是我在数据库中没有任何表,我也不允许创建一个。我只有对SQL views 之一的读取权限,以从数据库中获取数据并提供相应的响应。

以上是关于来自 SQL 视图的嵌套 JSON 响应的主要内容,如果未能解决你的问题,请参考以下文章

如何通过匹配来自两个 API 的 id 来查看来自两个 JSON API 的响应到单个回收器视图适配器中?

BigQuery SQL:将视图 A 中的子查询作为嵌套表嵌入视图 B

如何显示来自 JSON 的图像的水平滚动视图

想要来自休眠 json 响应的嵌套 json 格式

在 400 HTTP 响应时在警报视图上显示 API 响应 JSON 格式的错误结果

如何使用来自 api-platform 的 JSON