使用带有 Spring 的 JPA 或 HQL 执行自定义结果集的最佳方法是什么?

What is the best way to perform custom result sets using JPA or HQL with Spring?

我使用 Spring REST API 开发了一个小 Web 服务。我只是想知道使用 HQL 或 Criterias 从查询构建自定义数据结果集的最佳方法是什么。

假设我们需要处理这 2 个实体以执行以下 HQL 请求:

SELECT m.idMission, m.driver, m.dateMission FROM MissionEntity m

任务实体(简体):

@Entity
public class Mission
{
   Integer idMission;             //id of the mission
   String dateMission;            //date of the mission

   [...]                          //Other fields not needed for my request

   @ManyToOne
   @JoinColumn(name = "driver", 
        referencedColumnName = "id_user")
   User driver;                   //the driver (user) associated to the mission

   [...]                          //Accessors

};

和用户实体(驱动程序)(简化形式):

@Entity
public class User
{
   Integer idUser;                //id of the user

   [...]                          //Others fields not needed for my request

   @OneToMany
   List<Mission> missionList;     //the missions associated to the user

   [...]                          //Accessors
};

JSON 输出(第一个结果):

[
  [ //Mission: depth = 0 (root)
    1,
    { //Driver: depth = 1 (mission child -> User)
      "idUser": 29,
      "shortId": "Adr_Adr",
      "lastname": "ADRIAN",
      "firstname": null,
      "status": "Driver",
      "active": 1
    },
    "05/03/2015"
  ],

  [...]

]

如您所见,我有一个自定义任务实体结果集(列表),每个任务实体的模式如下:

+ Object
  - missionId (Integer)
  + driver (User)
     - idUser
     - shortId
     - lastname
     - firstname
     - status
     - active
  - dateMission (String)

但出于我的请求目的,我只需要用户实体的名字和姓氏。

所以我需要一个像下面这样的结果集:

+ Mission (Mission)
  - missionId (Integer)
  + driver (User)
     - lastname
     - firstname
  - dateMission (String)

如您所见,我想保持相同的 JSON 树结构:一个任务实体拥有一个子用户实体,但这次具有部分属性集(只需要名字和姓氏)在集合中)。

目前,解决我的问题的唯一方法是使用 2 个额外的 POJO classes:

UserProj class:

public class UserProj
{
   private String firstname, lastname;

   public UserProj(String firstname, String lastname)
   {
       this.firstname = firstname;
       this.lastname = lastname;
   }

   [...] //Accessors
};

MissionProj class:

public class MissionProj
{
   private Integer missionId;
   private UserProj driver;
   private String dateMission;

   public MissionProj(Integer missionId, 
            String driverFirstname, String driverLastname, String dateMission)
    {
        this.missionId = missionId;
        {
            this.driver = new UserProj(driverFirstname, driverLastname);
        }
        this.dateMission = dateMission;
    }

   [...]  //Accessors
};

下面是我用来获得希望的 JSON 输出结果集的请求:

[
  {
    "missionId": 1,
    "driver": {
      "firstname": null,
      "lastname": "ADRIAN"
    },
    "dateMission": "05/03/2015"
  },

  [...]

]

如您所见,结果集正是我要找的!但我对该解决方案的问题是该解决方案不可扩展。事实上,如果我想用一个额外的字段为用户或任务实体执行另一个自定义结果集,我将不得不为这个其他自定义结果集创建另一个 POJO。所以对我来说这个解决方案并不是真正的解决方案。

我认为应该存在一种直接使用 HQL 或 Criteria 正确执行此操作的方法,但我找不到它!你有什么想法吗?

非常感谢您的帮助!

As you can see, the result set is the one I was looking for! But my problem with that solution is that solution is not scalable. In fact, if I want to perform another custom result set for the User or the Mission entity with one additional field, I will have to create another POJO for this other custom result set. So for me this solution is not really a solution.

您确实在尝试将功能推到您的体系结构中太低的位置,这显然体现了您所描述的问题。

作为一些背景知识,HQL/JPQL 公开的 SELECT NEW 功能和 JPA 标准被引入作为查询可选对象并将它们注入值对象的简单方法通过正确选择正确的构造函数方法。调用者对构造的值对象所做的是一个应用程序问题,而不是持久性提供者的问题。

我相信一个更具可扩展性的解决方案是不依赖持久性提供程序来处理这个问题,而是直接将这个问题推回到 Jackson API 的上游,它已经被设计和构建来处理这个问题情况很容易。

您希望 Hibernate return 一个 List<Mission> 个对象,这些对象具有根据您的应用程序需要初始化的所有依赖对象的适当状态。然后,您可以让您的服务层或控制器使用自定义 Jackson 序列化程序转换此列表。

public class MissionSerializer extends StdSerializer<Mission> {
  private boolean serializeSpecialField;

  public MissionSerializer() {
    this( null );
  }

  public MissionSerializer(Class<Mission> clazz) {
    super( clazz );
  }

  public void setSerializeSpecialField(boolean value) {
    this.serializeSpecialField = value;
  }

  @Override
  public void serialize(
     Mission value, 
     JsonGenerator jgen, 
     SerializerProvider provider)
     throws IOException, JsonProcessingException {
    // use jgen to write the custom mappings of Mission
    // using the serializeSpecialField to control whether
    // you serialize that field.
  }      
}

然后在这一点上,这是获取 ObjectMapper 并在您的服务层或控制器中再次设置序列化程序并切换是否序列化额外字段的问题。

这里的好处是查询保持不变,允许持久性提供程序缓存查询中的实体,提高性能,同时允许应用层将结果转换为最终的目标输出。

此解决方案可能意味着您可能需要管理一个查询和一个 Jackson Serializer 来处理此任务,因此从技术债务来看,这可能是合理的。

另一方面,有很好的论据可以避免为了代码重用而耦合两个解决方案。

例如,如果您想更改一个用例怎么办?现在因为您重复使用相同的代码,一个用例更改意味着另一个用例受到影响,必须进行测试,并验证它仍然稳定且不受影响。通过不重用代码,您可以避免这种潜在风险,尤其是在您的测试套件没有完全覆盖的情况下。