【问题标题】:Load many-to-many relationship and map to entity加载多对多关系并映射到实体
【发布时间】:2020-10-28 23:09:49
【问题描述】:

我对 Spring Boot/Hibernate 开发还很陌生,这是我的第一个大项目。

我有一个应用程序使用 Spring Boot、Hibernate 和 Hibernate Envers 来审计一些实体。 Hibernate Envers 设置为使用 ValidityAuditStrategy。 由于 Hibernate Envers 尚不支持立即加载 to-Many 关系,我试图找到一种方法来执行单个查询并检索我需要的所有数据,以避免N+1 查询问题,目前正在扼杀我的表现:在我们的开发环境中,几乎需要 2 分钟才能完全加载我们需要的具有所需关系的实体。

由于我需要检索经过审核的版本,因此我无法像在应用程序的其他部分中使用的那样利用 EntityGraph(至少在我的理解中)。

我需要解决的一种情况是有 4 个实体 ParameterFormulaValueVariable,它们是这样相关的(仅显示相关部分,ValueVariable 是实体,未在此处列出)

@Entity
public class Parameter {
  @OneToOne(fetch = FetchType.LAZY, optional = false)
  @JoinColumn(name = "formula_id", referencedColumnName = "id", nullable = false)
  private Formula formula;
}

@Entity
public class Formula {
  @ManyToOne(optional = false)
  @JoinColumn(name = "tag_id")
  private Value tag;

  @ManyToMany
  @JoinTable(
      name = "Formulas_Variables",
      joinColumns = @JoinColumn(name = "formula_id"),
      inverseJoinColumns = @JoinColumn(name = "variable_id"))
  private Set<Variable> variables = new HashSet<>();
}

我尝试做的是创建一个自定义查询并使用 @NamedNativeQuery@SqlResultSetMapping 映射结果,但是,即使 Hibernate 正确创建了 FormulaValue 之间的关系,它也没有在variables 字段。我使用的查询和映射如下:

@SqlResultSetMapping(name = "Parameter.findAllByRevisionMapping", entities = {
    @EntityResult(entityClass = Parameter.class, fields = {
        @FieldResult(name = "id", column = "id"),
        @FieldResult(name = "formula", column = "formula_id")
    }),
    @EntityResult(entityClass = Formula.class, fields = {
        @FieldResult(name = "id", column = "formulaId"),
        @FieldResult(name = "tag", column = "tag_id"),
        @FieldResult(name = "variables", column = "variable_id")
    }),
    @EntityResult(entityClass = DomainValue.class, fields = {
        @FieldResult(name = "id", column = "tag_id")
    }),
    @EntityResult(entityClass = Variable.class, fields = {
        @FieldResult(name = "id", column = "variableId")
    })
})
@NamedNativeQuery(name = "Parameter.findAllByRevision", query = "SELECT om_c_p_aud.id,\n"
    + "       f_aud.id AS formulaId,\n"
    + "       dv_aud.id AS tag_id,\n"
    + "       fv_aud.formula_id AS formula_id,\n"
    + "       v_aud.id AS variable_id,\n"
    + "       v_aud.id AS variableId\n"
    + "FROM Parameters_AUD om_c_p_aud\n"
    + "         LEFT OUTER JOIN Formulas_AUD f_aud\n"
    + "                    ON f_aud.id = om_c_p_aud.formula_id AND f_aud.REV <= ?1 AND\n"
    + "                       f_aud.REVTYPE <> 2 AND (f_aud.REVEND > ?1 OR\n"
    + "                                               f_aud.REVEND IS NULL)\n"
    + "         LEFT OUTER JOIN Values_AUD dv_aud\n"
    + "                    ON dv_aud.id = f_aud.tag_id AND dv_aud.REV <= ?1 AND\n"
    + "                       dv_aud.REVTYPE <> 2 AND (dv_aud.REVEND > ?1 OR\n"
    + "                                                dv_aud.REVEND IS NULL)\n"
    + "         LEFT OUTER JOIN Formulas_Variables_AUD fv_aud\n"
    + "                    ON fv_aud.formula_id = f_aud.id AND fv_aud.REV <= ?1 AND\n"
    + "                       fv_aud.REVTYPE <> 2 AND (fv_aud.REVEND > ?1 OR\n"
    + "                                                fv_aud.REVEND IS NULL)\n"
    + "         LEFT OUTER JOIN Variables_AUD v_aud\n"
    + "                    ON v_aud.id = fv_aud.variable_id AND v_aud.REV <= ?1 AND\n"
    + "                       v_aud.REVTYPE <> 2 AND (v_aud.REVEND > ?1 OR\n"
    + "                                               v_aud.REVEND IS NULL)\n"
    + "WHERE om_c_p_aud.REV <= ?1\n"
    + "  AND om_c_p_aud.REVTYPE <> 2\n"
    + "  AND (om_c_p_aud.REVEND > ?1 OR\n"
    + "       om_c_p_aud.REVEND IS NULL)", resultSetMapping = "Parameter.findAllByRevisionMapping")

直接在数据库上执行的查询结果示例如下:

我在调用findAllByRevision 查询时收到的 json 对象的结果数组是这样的

[
   {
      "id":1,
      "formula":{
         "id":52,
         "tag":{
            "id":20
         },
         "variables":null
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":null
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":null
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":null
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":null
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":null
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":null
      }
   }
]

而我期待的是

[
   {
      "id":1,
      "formula":{
         "id":52,
         "tag":{
            "id":20
         },
         "variables":[
            {
               "id":4
            }
         ]
      }
   },
   {
      "id":2,
      "formula":{
         "id":88,
         "tag":{
            "id":24
         },
         "variables":[
            {
               "id":3
            },
            {
               "id":23
            },
            {
               "id":33
            },
            {
               "id":34
            },
            {
               "id":35
            },
            {
               "id":52
            }
         ]
      }
   }
]

有谁知道为什么不创建公式 变量关系?在使用 EntityGraph 时,我尝试检查 Hibernate 创建的查询以应对类似情况,在我看来,它与上面显示的查询相同。不过,我无法检查在这种情况下使用的映射(再次以我的理解)。

【问题讨论】:

    标签: java spring-boot hibernate spring-data-jpa hibernate-envers


    【解决方案1】:

    您应该能够使用 HQL 为已审核的关联指定连接提取。被审计的实体就像一个正常的实体。实体名称通常以“_AUD”为后缀,因此如果您想查询Parameters的审计信息,您可以查询Parameters_AUD,例如:

    SELECT p
    FROM Parameters_AUD p
    LEFT JOIN FETCH p.values
    LEFT JOIN FETCH p.variables
    

    【讨论】:

    • 那太好了,但是目前我没有审核过的类实体,如果我只是尝试按原样添加查询,则会出现错误。我应该为所有审计类创建实体吗?
    • 补充一点,如果我定义一个Parameter_AUD,我会在应用程序启动时收到以下错误`重复的实体映射com.myPackage.entities.Parameter_AUD`
    • 您不必为此定义类。当您为此类实体激活 Hibernate Envers 时,会在后台自动创建一个 MAP 模式实体。实体名称具有_AUD 后缀,因此您可以使用该名称进行查询。删除自定义定义的类。你得到什么错误?
    • 我得到的错误是Parameter_AUD 实体不存在。如果我创建了一个具有该名称的实体,则会收到重复的实体错误。所以过了一会儿,我尝试使用 fqn 并且它起作用了。尽管如此,我仍然遇到了一些问题,因为 REV、REVEND 和 REVTYPE 列在示例查询中无法访问,可能是因为它们嵌套在某个内部对象中。由于我努力让它以这种方式工作,我决定使用另一种方法并分别加载每个实体列表,并手动创建关系。我仍然有兴趣知道如何映射实体
    • 你说得对,实体名称是 FQN + "_AUD",抱歉我不够清楚。您可以检查实体元模型以了解如何访问此信息。每个 envers 实体都有一个 REVTYPEoriginalId 属性。 originalId 属性属于可嵌入类型,并具有子属性idREV。审核的关联通过其简单的连接列属性进行映射,因此如果需要,您需要进行实体连接以连接该信息。
    猜你喜欢
    • 1970-01-01
    • 2018-01-01
    • 2018-04-29
    • 2018-01-13
    • 2014-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多