简体   繁体   中英

Preserve Hibernate Lazy loading in data transfer object design pattern

I usually work on 3-tier applications using Hibernate in the persistence layer and I take care to not use the domain model classes in the presentation layer. This is why I use the DTO (Data Transfer Object) design pattern.

But I always have a dilemma in my entity-dto mapping. Whether I lose the lazy loading benefict, or I create complexity in the code by introducing filters to call or not the domain model getters .


Example : Consider a DTO UserDto that corresponds to the entity User

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 is used to filter the mapping and map just what is wanted.

Although the last solution allow lazy loading but it's very heavy because the optionList must be specified in the service layer.


Is there any better solution to preserve lazy loading in a DTO design pattern ?

For the same entity persistent state, I don't like having fields of an object un-initialized in some execution path, while these fields might also be initialized in other cases. This cause too much headache to maintain :

  • it will cause Nullpointer in the better cases
  • if null is also a valid option (and thus not cause NullPointer), it could mean the data was removed and might trigger unexpected removal business rules, while the data is in fact still there.

I would rather create a DTO hierarchy of interfaces and/or classes, starting with UserDto . All of the actual dto implementation fields are filled to mirror the persistent state : if there is data, the field of the dto is not null.

So then you just need to ask the service layer which implementation of the Dto you want :

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

Then in the service layer, you could have a :

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

where you register the different builders that will create and initialize the various UserDto implementations.

I'm not sure why you would want lazy loading, but I guess because your UserDto serves multiple representations through optionList configuration? I don't know how your presentation layer code looks like, but I guess you have lot's of if-else code for each element in optionList ?

How about having different representations ie subclasses instead? I'm asking this because I'd like to suggest giving Blaze-Persistence Entity Views a try. Here a little code example that fits to your domain.

@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();
}

These are the DTOs with some metadata about the mapping to the entity model. And here comes the usage:

@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;
}

Now if you use it like service.findByName("someName", EntityViewSetting.create(SimpleUserView.class)) it will generate a query like

SELECT u.id, u.firstname 
FROM User u 
WHERE u.name = :param_1

and if you use the other view like service.findByName("someName", EntityViewSetting.create(AuthorizationUserView.class)) it will generate

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

Apart from being able to get rid of the manual object mapping, the performance will improve because of using optimized queries!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM