在 Hibernate 会话之外执行 Mapstruct 映射
Perform Mapstruct mapping outside of Hibernate session
我正在使用 Spring Data 和 Mapstruct,我不希望 hibernate 在将实体映射到 dto 时盲目地加载所有元素。
示例:
public class VacancyEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "job_category_id", nullable = false)
JobCategoryEntity jobCategory;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "company_id", nullable = false)
CompanyEntity company;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "employer_created_by", nullable = false)
EmployerProfileEntity employerCreatedBy;
@Column(nullable = false)
String title;
.... }
DTO:
public class VacancyDto {
Integer id;
String title;
CompanyDto company;
EmployerProfileDto employerCreatedBy;
JobCategoryDto jobCategory;
...}
所以我在VacancyRepository
中有两种方法findByIdWithCompanyAndCity
和findByIdWithJobAndCityAndEmployer
只执行一个SQL请求。
我的 VacancyService
中有两个 @Transactional
方法:findWithCompanyAndCity
和 findWithCompanyAndCityAndEmployer
。
最好的做法是从Service层返回Dto,所以我们需要在Service中将Entity解析为Dto。
而且我真的不想将整个映射留在 @Transactional
(会话)中,因为如果我将一些字段真正深入到我的实体中,Mapstruct 只会触发 N+1 问题.
我想出的最好办法是将每个内部实体包含到方法中并手动检查 Mapstruct 是否添加了一些新方法。 (比检查名字更快)
例如:
@Mapping(target = "id", source = "entity.id")
@Mapping(target = "description", source = "entity.description")
@Mapping(target = "jobCategory", source = "jobCategoryDto")
@Mapping(target = "employerCreatedBy", source = "employerProfileDto")
@Mapping(target = "city", source = "cityDto")
@Mapping(target = "company", ignore = true)
VacancyDto toDto(VacancyEntity entity,
JobCategoryDto jobCategoryDto,
EmployerProfileDto employerProfileDto,
CityDto cityDto);
....
但这并没有解决真正的问题。映射的时候还有session,所以会导致N+1的问题。
于是想出了几个办法
- 在服务中使用特殊方法触发@Transactional 方法,然后映射到会话范围外的DTO。但是在 Service
中将方法加倍似乎真的很难看
- Return 来自服务的实体(这是不好的做法)并在那里映射到 DTO。
我知道在这两种情况下我都会得到 LazyInitializationException
,但在我看来它比 SELECT.
更健壮和可扩展
如何在服务层但在 Hibernate 会话之外以优雅的方式执行从实体到 DTO 的映射?
你没有问问题,但问题应该是:
How do I perform the mapping from entity to DTO in the service layer but outside the Hibernate session in an elegant way.
为此,我推荐 TransactionTemplate
。
用法如下所示:
@Autowired
VacancyRepository repo;
@Autowired
TransactionTemplate tx;
void someMethod(String company, String city){
VacancyEntity vac = tx.execute(__ -> repo.findWithCompanyAndCity(company, city));
return mappToDto(vac);
}
也就是说,我认为您使用错误的方法来解决根本问题。
我建议您看一下测试以验证执行的 SQL 语句的数量。
请参阅 https://vladmihalcea.com/how-to-detect-the-n-plus-one-query-problem-during-testing/ 以了解执行此操作的方法。
为了避免 N + 1 问题,您仍然需要使用实体图,尽管我认为这是 Blaze-Persistence Entity Views.
的完美用例
我创建了库以允许在 JPA 模型和自定义接口或抽象 class 定义的模型之间轻松映射,类似于 Spring 类固醇数据投影。这个想法是您按照自己喜欢的方式定义目标结构(领域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。
您的用例的 DTO 模型与 Blaze-Persistence 实体视图类似:
@EntityView(VacancyEntity.class)
public interface VacancyDto {
@IdMapping
Integer getId();
String getTitle();
CompanyDto getCompany();
EmployerProfileDto getEmployerCreatedBy();
JobCategoryDto getJobCategory();
@EntityView(CompanyEntity.class)
interface CompanyDto {
@IdMapping
Integer getId();
String getName();
}
@EntityView(EmployerProfileEntity.class)
interface EmployerProfileDto {
@IdMapping
Integer getId();
String getName();
}
@EntityView(JobCategoryEntity.class)
interface JobCategoryDto {
@IdMapping
Integer getId();
String getName();
}
}
查询就是将实体视图应用于查询,最简单的就是通过 id 进行查询。
VacancyDto a = entityViewManager.find(entityManager, VacancyDto.class, id);
Spring 数据集成让您几乎可以像 Spring 数据投影一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<VacancyDto> findAll(Pageable pageable);
最棒的是,它只会获取实际需要的状态!
我正在使用 Spring Data 和 Mapstruct,我不希望 hibernate 在将实体映射到 dto 时盲目地加载所有元素。
示例:
public class VacancyEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "job_category_id", nullable = false)
JobCategoryEntity jobCategory;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "company_id", nullable = false)
CompanyEntity company;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "employer_created_by", nullable = false)
EmployerProfileEntity employerCreatedBy;
@Column(nullable = false)
String title;
.... }
DTO:
public class VacancyDto {
Integer id;
String title;
CompanyDto company;
EmployerProfileDto employerCreatedBy;
JobCategoryDto jobCategory;
...}
所以我在VacancyRepository
中有两种方法findByIdWithCompanyAndCity
和findByIdWithJobAndCityAndEmployer
只执行一个SQL请求。
我的 VacancyService
中有两个 @Transactional
方法:findWithCompanyAndCity
和 findWithCompanyAndCityAndEmployer
。
最好的做法是从Service层返回Dto,所以我们需要在Service中将Entity解析为Dto。
而且我真的不想将整个映射留在 @Transactional
(会话)中,因为如果我将一些字段真正深入到我的实体中,Mapstruct 只会触发 N+1 问题.
我想出的最好办法是将每个内部实体包含到方法中并手动检查 Mapstruct 是否添加了一些新方法。 (比检查名字更快) 例如:
@Mapping(target = "id", source = "entity.id")
@Mapping(target = "description", source = "entity.description")
@Mapping(target = "jobCategory", source = "jobCategoryDto")
@Mapping(target = "employerCreatedBy", source = "employerProfileDto")
@Mapping(target = "city", source = "cityDto")
@Mapping(target = "company", ignore = true)
VacancyDto toDto(VacancyEntity entity,
JobCategoryDto jobCategoryDto,
EmployerProfileDto employerProfileDto,
CityDto cityDto);
....
但这并没有解决真正的问题。映射的时候还有session,所以会导致N+1的问题。
于是想出了几个办法
- 在服务中使用特殊方法触发@Transactional 方法,然后映射到会话范围外的DTO。但是在 Service 中将方法加倍似乎真的很难看
- Return 来自服务的实体(这是不好的做法)并在那里映射到 DTO。
我知道在这两种情况下我都会得到 LazyInitializationException
,但在我看来它比 SELECT.
如何在服务层但在 Hibernate 会话之外以优雅的方式执行从实体到 DTO 的映射?
你没有问问题,但问题应该是:
How do I perform the mapping from entity to DTO in the service layer but outside the Hibernate session in an elegant way.
为此,我推荐 TransactionTemplate
。
用法如下所示:
@Autowired
VacancyRepository repo;
@Autowired
TransactionTemplate tx;
void someMethod(String company, String city){
VacancyEntity vac = tx.execute(__ -> repo.findWithCompanyAndCity(company, city));
return mappToDto(vac);
}
也就是说,我认为您使用错误的方法来解决根本问题。 我建议您看一下测试以验证执行的 SQL 语句的数量。 请参阅 https://vladmihalcea.com/how-to-detect-the-n-plus-one-query-problem-during-testing/ 以了解执行此操作的方法。
为了避免 N + 1 问题,您仍然需要使用实体图,尽管我认为这是 Blaze-Persistence Entity Views.
的完美用例我创建了库以允许在 JPA 模型和自定义接口或抽象 class 定义的模型之间轻松映射,类似于 Spring 类固醇数据投影。这个想法是您按照自己喜欢的方式定义目标结构(领域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。
您的用例的 DTO 模型与 Blaze-Persistence 实体视图类似:
@EntityView(VacancyEntity.class)
public interface VacancyDto {
@IdMapping
Integer getId();
String getTitle();
CompanyDto getCompany();
EmployerProfileDto getEmployerCreatedBy();
JobCategoryDto getJobCategory();
@EntityView(CompanyEntity.class)
interface CompanyDto {
@IdMapping
Integer getId();
String getName();
}
@EntityView(EmployerProfileEntity.class)
interface EmployerProfileDto {
@IdMapping
Integer getId();
String getName();
}
@EntityView(JobCategoryEntity.class)
interface JobCategoryDto {
@IdMapping
Integer getId();
String getName();
}
}
查询就是将实体视图应用于查询,最简单的就是通过 id 进行查询。
VacancyDto a = entityViewManager.find(entityManager, VacancyDto.class, id);
Spring 数据集成让您几乎可以像 Spring 数据投影一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<VacancyDto> findAll(Pageable pageable);
最棒的是,它只会获取实际需要的状态!