what's a workaround if I have a relation OneToMany
and would like to access the collection that is lazy loaded? Currently I get LazyInitializationException
having this:
Club
entity:
@Entity
public class Club {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "club", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JsonBackReference
private List<Player> players;
Player
entity:
Is it a good idea to have two methods, where one fetches data without players and the second one that fetches also players?
@Override
List<Club> findAll();
@Query("Select clubs from Club clubs left join fetch clubs.players")
List<Club> getAllClubsWithPlayers();
What I'm thinking of is that it is a bad idea, because if I have a situation where I have for example 4 properties that are lazy loaded and I'd need at once 3 of them, I'd have to have a query like: getAllClubsWithPlayersAndSponsorsAndCoaches
, so I'd have to have a lot of combinations of such queries.
I don't want to use EAGER
, so could you tell me if it's a common way to do this if I need access to players
from the Club
sometimes which is undoable with the findAll()
method that throws LazyInitializationException
?
Don't understand me wrong - I know where the LazyInitializationException
comes from, but I just don't know what's the best way to get access to players
if I sometimes need them when fetching clubs
. Is my way of doing it correct?
CodePudding user response:
There are 3 choices:
- Access all the lazy fields inside a
@Transactional
method. You don't show your code, but there's usually a Service Facade layer which is responsible for being@Transactional
. It invokes Repositories. - Write a query that fetches all the required data. Then you'd have to create a method specifically to fetch all the lazy fields required for that logic.
- Use OpenSessionInViewFilter or OpenSessionInViewInterceptor so that Session/EntityManager are started before the execution even reaches the Controller. The Session then would be closed by the same high-level layer at the end of the request processing.
CodePudding user response:
In addition to what Stanislav wrote, I'd like to elaborate on his 2nd point, because I think that this is often the best approach - that's simply because it saves unnecessary calls to the database which results in better performance.
Apart from writing separate JPQL query in your repository for each use-case, you could do one of the following .:
Make your repository extend
JpaSpecificationExecutor
and programmatically describe what needs to be fetched as described in this answerUse Entity Graph either described using annotations, or programmatically, and fetch your entities using
EntityManager
as described in this tutorial
CodePudding user response:
To optionally load what you want you can use EntityGraph.
Declare @NamedEntityGraph
at your entity
@Entity
@NamedEntityGraph(name = "Club.players",
attributeNodes = @NamedAttributeNode("players")
)
public class Club {
Then you should annotate your findAll()
method with this graph using it's name
@EntityGraph(value = "Club.players")
List<Club> findAll();
However that would override your basic findAll()
method.
To avoid this (to have both implementations) you can follow this way:
Add dependency from https://mvnrepository.com/artifact/com.cosium.spring.data/spring-data-jpa-entity-graph/<version>
Then replace your repository with
@Repository
public interface ClubRepository extends JpaSpecificationExecutor<Club>, JpaRepository<Club, Long>, EntityGraphJpaSpecificationExecutor<Club> {
}
And then you'll have basic method findAll()
and also from your sevice you can call
List<Club> clubs = clubRepository.findAll(specification, new NamedEntityGraph(EntityGraphType.FETCH, "Club.players"))