在数据传输对象设计模式中保留 Hibernate 延迟加载

Preserve Hibernate Lazy loading in data transfer object design pattern

我通常在持久层使用 Hibernate 开发 3 层应用程序,我注意不要在表示层使用域模型 类。这就是我使用 DTO (Data Transfer Object) 设计模式的原因。

但我的entity-dto映射总是进退两难。我是否失去了延迟加载的好处,或者我通过引入过滤器来调用或不调用域模型来在代码中创建复杂性 getters.


示例: 考虑对应于实体 User

的 DTO UserDto
public UserDto toDto(User entity, OptionList... optionList) {

        if (entity == null) {
            return null;
        }

        UserDto userDto = new UserDto();
        userDto.setId(entity.getId());
        userDto.setFirstname(entity.getFirstname());


        if (optionList.length == 0 || optionList[0].contains(User.class, UserOptionList.AUTHORIZATION)) {
            IGenericEntityDtoConverter<Authorization, AuthorizationDto> authorizationConverter = converterRegistry.getConverter(Authorization.class);

            List<AuthorizationDto> authorizations = new ArrayList<>(authorizationConverter.toListDto(entity.getAuthorizations(), optionList));
            userDto.setAuthorizations(authorizations);

...
}

OptionList用于过滤映射,只映射需要的内容。

最后一个方案虽然允许延迟加载但是非常繁重,因为optionList必须在服务层指定。


在 DTO 设计模式中是否有更好的解决方案来保留延迟加载?

对于同一个实体持久化状态,我不喜欢在某些执行路径中有一个对象的字段未初始化,而这些字段在其他情况下也可能被初始化。这导致维护起来太头疼了:

  • 在更好的情况下会导致空指针
  • 如果 null 也是一个有效选项(因此不会导致 NullPointer),这可能意味着数据已被删除并可能触发意外删除业务规则,而数据实际上仍然存在。

我宁愿创建接口 and/or 类 的 DTO 层次结构,从 UserDto 开始。所有实际的 dto 实现字段都被填充以反映持久状态:如果有数据,dto 的字段不为空。

那么你只需要询问服务层你想要哪个 Dto 实现:

public <T extends UserDto> T toDto(User entity, Class<T> dtoClass) {
    ...
}

然后在服务层,你可以有一个:

Map<Class<? extends UserDto>, UserDtoBUilder> userDtoBuilders = ...

在其中注册将创建和初始化各种 UserDto 实现的不同构建器。

我不确定您为什么要延迟加载,但我猜是因为您的 UserDto 通过 optionList 配置提供多种表示形式? 我不知道您的表示层代码是什么样的,但我猜您对 optionList?

中的每个元素都有很多 if-else 代码

使用不同的表示方式(即子类)怎么样?我问这个是因为我想建议 Blaze-Persistence Entity Views 试一试。这里有一个适合您的域的小代码示例。

@EntityView(User.class)
public interface SimpleUserView {
    // The id of the user entity
    @IdMapping("id") int getId();

    String getFirstname();
}

@EntityView(Authorization.class)
public interface AuthorizationView {

    // The id of the authorization entity
    @IdMapping("id") int getId();

    // Whatever properties you want
}

@EntityView(User.class)
public interface AuthorizationUserView extends SimpleUserView {

    List<AuthorizationView> getAuthorizations();
}

这些是 DTO,其中包含一些关于映射到实体模型的元数据。用法来了:

@Transactional
public <T> T findByName(String name, EntityViewSetting<T, CriteriaBuilder<T>> setting) {
    // Omitted DAO part for brevity

    EntityManager entityManager = // jpa entity manager
    CriteriaBuilderFactory cbf = // query builder factory from Blaze-Persistence
    EntityViewManager evm = // manager that can apply entity views to query builders

    CriteriaBuilder<User> builder = cbf.create(entityManager, User.class)
        .where("name").eq(name);
    List<T> result = evm.applySetting(builder, setting)
        .getResultList();
    return result;
}

现在如果你像service.findByName("someName", EntityViewSetting.create(SimpleUserView.class))那样使用它,它会生成像

这样的查询
SELECT u.id, u.firstname 
FROM User u 
WHERE u.name = :param_1

如果您使用其他视图,例如 service.findByName("someName", EntityViewSetting.create(AuthorizationUserView.class)),它将生成

SELECT u.id, u.firstname, a.id 
FROM User u LEFT JOIN u.authorizations a 
WHERE u.name = :param_1

除了能够摆脱手动对象映射之外,由于使用了优化的查询,性能也会有所提高!