开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情
MapStruct用于在 Java bean 之间进行映射。甚至像Dozer Mapper这样的替代映射器框架也推荐将 MapStruct 作为当今使用的工具。MapStruct 可以作为编译器插件包含在您的构建中,并将自动为 bean 类型生成映射器(在编译期间,而不是在构建期间,就像 Dozer 那样)。将映射框架与 Hibernate/JPA 实体一起使用时,需要考虑的一个重要情况是延迟加载的情况。假设,以下类层次结构:
我们看到一个 Hibernate/JPA 实体 bean 的层次结构,它们可能用于持久层,以及一个具有相同结构的 DTO 层次结构用于业务层。默认情况下,地址或BankAccount等子集合应该在 Hibernate 中“延迟”获取。“急切”获取总是会导致读取大量数据——尤其是在较大的类层次结构中——我们希望避免这种情况。只应从数据库中获取必要的数据,而且通常情况下,只需要主要实体(例如,如果要检查对象的状态)。
当使用常规的 MapStruct 映射器时,映射器不仅会将 PersonEntity 的字段映射到PersonDTO,还会访问地址和银行账户等子集合并映射它们。访问这些子集合将触发 Hibernate/JPA 延迟加载机制并对这些集合进行延迟加载。这样做的缺点是导致许多单独的 SQL Select 语句加载子集合的额外数据。这也称为“Hibernate N+1 问题”(例如,参见hackernoon.com/3-ways-to-d…)。
有两种可能的解决方案来解决此问题:
- 使用 JPA 查询预先加载所有必需的数据,例如“ Select e from PersonEntity e Left join fetch e.addresses Left join fetch e.bankAccounts where e.id = :id ”。现在,数据已经存在于实体中并且 MapStruct 可以映射它,而不会触发延迟加载。
- 我们指示 MapStruct 跳过 Hibernate/JPA 中的惰性集合。
我们想遵循本文中的第二种方法。我们方法的先决条件是1.5.3版本,MapStruct 团队修复了一个与条件处理相关的重要错误(问题 2937 )。基于此修复,我们可以实现一个接口来处理 Hibernate/JPA 中未初始化的集合:
public interface LazyLoadingAwareMapper {
default boolean isNotLazyLoaded(Collection<?> sourceCollection){
if (Hibernate.isInitialized(sourceCollection)) {
return true ;
}
return false ;
}
}
现在,我们可以在我们的映射器中实现这个接口:
@Mapper(uses = { AddressMapper.class, BankAccountMapper.class }, config = MapStructConfiguration.class)
public interface PersonMapper extends LazyLoadingAwareMapper {
PersonDTO toDTO( final PersonEntity entity);
PersonEntity toEntity(最终PersonDTO dto);
@Condition
default boolean isNotLazyLoadedBankAccount(
Collection<BankAccount> sourceCollection) {
返回isNotLazyLoaded(sourceCollection);
}
@Condition
default boolean isNotLazyLoadedAddress(
Collection<Address> sourceCollection) {
return isNotLazyLoaded(sourceCollection); }
}
}
也就是说,我们已经为两个集合定义了额外的条件,我们希望避免触发延迟加载。为 Person 生成的映射器正确地实现了检查:
@Override public PersonDTO toDTO(PersonEntity entity) { if ( entity == null ) { return null ; }
PersonDTO personDTO = new PersonDTO();
personDTO.setFirstname( entity.getFirstname() );
personDTO.setLastname( entity.getLastname() );
personDTO.setBirthday( entity.getBirthday() );
if ( isNotLazyLoadedAddress( entity.getAddresses() ) ) {
for ( AddressEntity address : entity.getAddresses() ) {
personDTO.addAddress( addressMapper.toDTO( address ) );
}
}
if (isNotLazyLoadedBankAccount( entity.getBankAccounts() ) ) {
for (BankAccountEntity bankAccount :
entity.getBankAccounts() ) {
personDTO.addBankAccount(
bankAccountMapper.toDTO(bankAccount));
}
}
返回人DTO;
}
最后,我们最终得到这样的类层次结构:
可以想到一种简化,将 @Condition直接放在父接口中的“ isNotLazyLoaded ”方法上,但这会导致方法二义性错误。这就是为什么我们明确定义要延迟处理哪些收集袋(以及您可能希望保留默认行为)的原因:
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] PersonMapper.java:[11,27] Ambiguous presence check methods found for checking Set<AddressEntity>: boolean isNotLazyLoaded(Collection sourceCollection), boolean AddressMapper.isNotLazyLoaded(Collection sourceCollection), boolean BankAccountMapper.isNotLazyLoaded(Collection sourceCollection). See https://mapstruct.org/faq/#ambiguous for more info.
[ERROR] PersonMapper.java:[11,27] Ambiguous presence check methods found for checking Set<BankAccountDTO>: boolean isNotLazyLoaded(Collection sourceCollection), boolean AddressMapper.isNotLazyLoaded(Collection sourceCollection), boolean BankAccountMapper.isNotLazyLoaded(Collection sourceCollection). See https://mapstruct.org/faq/#ambiguous for more info.
[ERROR] PersonMapper.java:[12,24] Ambiguous presence check methods found for checking Set<AddressDTO>: boolean isNotLazyLoaded(Collection sourceCollection), boolean AddressMapper.isNotLazyLoaded(Collection sourceCollection), boolean BankAccountMapper.isNotLazyLoaded(Collection sourceCollection). See https://mapstruct.org/faq/#ambiguous for more info.
[ERROR] PersonMapper.java:[12,24] Ambiguous presence check methods found for checking Set<BankAccountDTO>: boolean isNotLazyLoaded(Collection sourceCollection), boolean AddressMapper.isNotLazyLoaded(Collection sourceCollection), boolean BankAccountMapper.isNotLazyLoaded(Collection sourceCollection). See https://mapstruct.org/faq/#ambiguous for more info.
在本文中,我们展示了如何指示 MapStruct 忽略来自 Hibernate/JPA 的延迟加载集合。这可以如上所示实现,并在我们的示例中与 DTO 模式一起使用,但也可用于其他场景。