有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

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

但我在实体dto映射中总是进退两难。无论我是丢失了延迟加载功能,还是通过引入过滤器来调用或不调用域模型getters,在代码中造成了复杂性


示例:考虑与实体用户相对应的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设计模式中保留延迟加载是否有更好的解决方案


共 (2) 个答案

  1. # 1 楼答案

    我不确定您为什么希望延迟加载,但我猜是因为您的UserDto通过optionList配置提供多个表示? 我不知道您的表示层代码是什么样子的,但我想您对optionList中的每个元素都有很多if-else代码

    用不同的表示法,比如子类,怎么样?我问这个是因为我想建议尝试一下。下面是一个适合您的领域的小代码示例

    @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
    

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

  2. # 2 楼答案

    对于相同的实体持久状态,我不喜欢在某些执行路径中有未初始化的对象字段,而在其他情况下,这些字段也可能被初始化。这会导致头痛,无法维持:

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

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

    因此,您只需要询问服务层您想要哪个Dto实现:

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

    然后在服务层,您可以有:

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

    注册不同的构建器,创建并初始化各种UserDto实现