Issue
I am currently building a REST service with Micronaut Data. I have defined two JPA entities, bounded by a bidirectional @OneToMany
relationship, and lazy loading.
@Entity
@Getter
@Setter
public class ScoringEntity {
@Id
private String id;
@OneToMany(mappedBy = "scoring", fetch = FetchType.LAZY)
private Set<ContributionEntity> contributions;
}
@Entity
@Getter
@Setter
public class ContributionEntity {
@Id
private String id;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId
private ScoringEntity scoring;
}
@Repository
public interface ScoringRepository extends GenericRepository<ScoringEntity, String> {
Page<ScoringEntity> findAll(Pageable pageable);
}
In the controller, I return a Mono
which is set to call the repository, then perform a mapping to a DTO (Scoring
).
@Get
public Mono<Page<Scoring>> getScoring(Pageable pageable) {
return Mono.fromCallable(() -> scoringRepository.findAll(pageable))
.map(scoringEntities -> scoringEntities.map(scoringMapper::mapScoring));
}
Everything works fine in the findAll()
method, but things go south when the mapper
tries to access the contributions set :
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: ScoringEntity.contributions, could not initialize proxy - no Session
While I understand why this happens (the transaction probably ends with the repository method), I can't find a satisfying solution. Setting the relationship to eager loading works, but it significantly decreases performance (and I've read elsewhere it would be a code smell, which I tend to believe).
Besides I can't imagine that reactive streams be incompatible with hibernate and lazy loading.
What would be the best practice in this situation ?
Solution
There are a few options:
- Add
@Join("contributions")
annotation to your repository method - Add
@EntityGraph ...
annotation to your repository method - Do fetching and mapping in one method annotated
@ReadOnly
or@Transactional
so the session is open when the mapper is called
In this case, having reactive streams doesn't make much sense. Just return Page
from the controller annotated @ExecuteOn(TaskExecutors.IO)
.
Answered By - Denis
Answer Checked By - David Marino (JavaFixing Volunteer)