【问题标题】:Avoid N+1 with DTO mapping on Hibernate entities在 Hibernate 实体上使用 DTO 映射避免 N+1
【发布时间】:2014-08-03 06:01:18
【问题描述】:

在我们的 Restful 应用程序中,出于几个原因,我们决定使用 DTO 来屏蔽 Hibernate 域模型。 我们使用Service Layer 中的DTOMappers 手动将Hibernate 实体映射到DTO,反之亦然。

服务层示例:

@Transactional(readOnly=true)
public PersonDTO findPersonWithInvoicesById(Long id) {
    Person person = personRepository.findById(id);
    return PersonMapperDTOFactory.getInstance().toDTO(person);
}

主要概念可以这样解释:

JSON (Jackson parser) <-> Controller <-> Service Layer (uses Mapping Layer) <-> Repository

我们同意通过使用left join 执行HQL(或Criteria)来检索关联。 这主要是一种检索关系并避免N+1 select issue 的高效方式。

但是,当开发人员错误地忘记进行左连接时,仍然可以使用N+1 select issue。仍然会获取关系,因为PersonDTOMapper 将遍历PersonInvoices 以转换为InvoiceDTOs。所以仍然会获取数据,因为DTOMapper 是在 Hibernate Session 处于活动状态的地方执行的(由Spring 管理)

有什么方法可以让我们的DTOMappers 中的Hibernate Session“不活跃”吗?我们将面临一个LazyInitializationException,这应该会触发开发人员他没有按预期获取一些数据。

我读过关于暂停交易的@Transactional(propagation = Propagation.NOT_SUPPORTED)。但是,我不知道它是用于此类目的的。

实现此目的的干净解决方案是什么?也非常欢迎替代品!

【问题讨论】:

    标签: spring hibernate jpa transactions dto


    【解决方案1】:

    实现您想要的最简单的解决方案是在查询之后和调用 DTO 映射器之前清除实体管理器。这样,对象将被分离并且对未初始化关联的访问将触发LazyInitializationException

    我也感受到了您的痛苦,这促使我开发了Blaze-Persistence Entity Views,它允许您将 DTO 定义为接口并映射到实体模型,使用属性名称作为默认映射,这允许看起来非常简单的映射。

    这里有一个小例子

    @Entity
    class Person {
      @Id Long id;
      String name;
      String lastName;
      String address;
      String city;
      String zipCode;
    }
    
    @EntityView(Person.class)
    interface PersonDTO {
      @IdMapping Long getId();
      String getName();
    }
    

    查询很简单

    @Transactional(readOnly=true)
    public PersonDTO findPersonWithInvoicesById(Long id) {
        return personRepository.findById(id);
    }
    
    interface PersonRepository extends EntityViewRepository<PersonDTO, Long> {
      PersonDTO findById(Long id);
    }
    

    由于您似乎使用的是 Spring 数据,因此您会喜欢 spring data integration

    【讨论】:

      【解决方案2】:

      如果您使用的是 Hibernate,则可以通过特定方法确定关联对象是否已延迟加载。

      例如,假设您有一个实体类Foo,其中包含与实体类Bar@ManyToOne“外部”关联,该关联由Foo 中名为bar 的字段表示。

      在您的 DTO 映射代码中,您可以使用以下代码检查关联的 bar 是否已被延迟加载:

          if (!(bar instanceof HibernateProxy) ||
                 !((HibernateProxy)bar).getHibernateLazyInitializer().isUninitialized()) {
              // bar has already been lazy-loaded, so we can
              // recursively load a BarDTO for the associated Bar object
          }
      

      【讨论】:

        【解决方案3】:

        通常我在控制器层使用映射器。从我的角度来看,服务层管理应用程序的业务逻辑,如果你想以不同的方式将数据呈现给外部世界,dtos 非常有用。通过这种方式,您可能会得到您正在寻找的惰性初始化异常。

        我还有一个更喜欢这个解决方案的理由:只是图像,您需要在服务类的公共方法中调用公共方法:在这种情况下,您可能需要多次调用映射器。

        【讨论】:

          猜你喜欢
          • 2021-10-28
          • 2018-09-13
          • 1970-01-01
          • 2012-05-19
          • 2022-01-14
          • 1970-01-01
          • 2017-09-17
          • 1970-01-01
          相关资源
          最近更新 更多