I am using Spring Data and Mapstruct and I don't want hibernate to blindly load all the elements while mapping entity to dto.
Example:
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;
...}
So I have two methods findByIdWithCompanyAndCity and findByIdWithJobAndCityAndEmployer in VacancyRepository to perform only one SQL request.
And two @Transactional methods in my VacancyService: findWithCompanyAndCity and findWithCompanyAndCityAndEmployer.
Best practice is returning Dto from Service layer, so we need to parse Entity to Dto in the Service.
And I really don't want to just leave whole mapping in @Transactional (session) because if I add some field really deep into my entity, Mapstruct just trigger N+1 problem.
Best that I came up with, is to include each inner entity into method and check manually that Mapstruct don't add some new methods. (it is faster then checking names) Ex:
@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);
....
But this doesn't fix the real issue. There are still session while mapping, so it can lead to N+1 problem.
So I came up with several solutions
- Use special method in Service to trigger @Transactional method and then map into DTO out of session scope. But it seems really ugly to double methods in Service
- Return Entity from Service (which is Bad Practice) and map into DTO there.
I know that I'll get LazyInitializationException in both cases, but it seems to me like it more robust and scalable then just unpredictably SELECT.
How do I perform the mapping from entity to DTO in the service layer but outside the Hibernate session in an elegant way?