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 :
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.