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 ?
CodePudding user response:
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)
.
CodePudding user response:
Mapping the association as EAGER
is usually a code smell. But if you know that in specific part of the code you always want to load an association you can change the query for that specific case.
So, instead of findAll
, you would call:
@Repository
public interface ScoringRepository extends GenericRepository<ScoringEntity, String> {
@Query("select se from ScoringEntity se left fetch join se.contributions")
Page<ScoringEntity> findScoringWithContributions(Pageable pageable);
}
For situation when you don't always need the association, you can get it separately via a repository for ContributionEntity
. Or by making sure that all operations happen inside a @Transactional
method.
Besides I can't imagine that reactive streams be incompatible with hibernate and lazy loading.
In this case the problem is that you are trying to lazy load the association after the session has been closed.