我通常在持久层中使用Hibernate处理3层应用程序,我注意不要在表示层中使用域模型类。这就是我使用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设计模式中是否有更好的解决方案来保留延迟加载?
答案 0 :(得分:1)
对于相同的实体持久状态,我不喜欢在某个执行路径中取消初始化对象的字段,而在其他情况下也可能初始化这些字段。这导致太难以维持:
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实现的不同构建器。
答案 1 :(得分:0)
我不确定你为什么要延迟加载,但我猜是因为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();
}
这些是 DTOs ,其中包含有关实体模型映射的一些元数据。以下是用法:
@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
除了能够摆脱手动对象映射外,由于使用了优化的查询,性能也会提高!