英文:
OneToMany lazy initialization when needing collection data
问题
如果我有一个OneToMany关系,并希望访问延迟加载的集合,那么有什么解决方法呢?当前我在这种情况下会得到LazyInitializationException:
Club实体:
@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实体:
是否有一个好主意,可以创建两个方法,一个获取没有玩家数据的数据,另一个获取包含玩家数据的数据?
@Override
List<Club> findAll();
@Query("Select clubs from Club clubs left join fetch clubs.players")
List<Club> getAllClubsWithPlayers();
我考虑的是这样做可能不是个好主意,因为如果我有一个情况,其中有4个属性是延迟加载的,我需要同时获取其中的3个,我就必须有一个查询,像这样:getAllClubsWithPlayersAndSponsorsAndCoaches,所以我必须有很多这样的查询组合。我不想使用EAGER,那么您能告诉我,如果我有时需要访问Club中的players,而findAll()方法会抛出LazyInitializationException,这种做法是否常见?
不要误会我的意思 - 我知道LazyInitializationException是从哪里来的,但我只是不知道如何最好地访问players,如果有时在获取clubs时需要它们。我的做法是否正确?
英文:
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 get 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?
答案1
得分: 1
- 在
@Transactional方法中访问所有的惰性字段。您没有展示您的代码,但通常有一个负责进行@Transactional操作的服务门面层。它会调用存储库。 - 编写一个查询,获取所有所需的数据。然后,您需要创建一个专门用于获取所有与该逻辑所需的惰性字段的方法。
- 使用OpenSessionInViewFilter或OpenSessionInViewInterceptor,以便在执行到达控制器之前启动Session/EntityManager。然后,在请求处理结束时,同一高级层将关闭该会话。
英文:
There are 3 choices:
- Access all the lazy fields inside a
@Transactionalmethod. 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.
答案2
得分: 0
除了Stanislav所写的内容之外,我想详细说明他的第二点,因为我认为这通常是最佳方法 - 这仅仅是因为它可以节省不必要的数据库调用,从而提高性能。
除了为每个用例在存储库中编写单独的JPQL查询之外,您可以执行以下之一:
-
使您的存储库扩展
JpaSpecificationExecutor,并以编程方式描述需要获取的内容,如此答案中所述。 -
使用实体图,可以使用注释或以编程方式描述,然后使用
EntityManager获取您的实体,如此教程中所述。
英文:
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
JpaSpecificationExecutorand programmatically describe what needs to be fetched as described in this answer -
Use Entity Graph either described using annotations, or programmatically, and fetch your entities using
EntityManageras described in this tutorial
答案3
得分: 0
要选择性加载所需内容,您可以使用 EntityGraph。
在您的实体上声明 @NamedEntityGraph
@Entity
@NamedEntityGraph(name = "Club.players",
attributeNodes = @NamedAttributeNode("players")
)
public class Club {
然后,您应该在您的 findAll() 方法上使用此图进行注释,使用它的名称:
@EntityGraph(value = "Club.players")
List<Club> findAll();
但是,这会覆盖您的基本 findAll() 方法。
要避免这种情况(同时拥有两种实现),您可以按照以下方式操作:
添加依赖项来自 https://mvnrepository.com/artifact/com.cosium.spring.data/spring-data-jpa-entity-graph/<version>
然后用以下内容替换您的存储库
@Repository
public interface ClubRepository extends JpaSpecificationExecutor<Club>, JpaRepository<Club, Long>, EntityGraphJpaSpecificationExecutor<Club> {
}
然后,您将拥有基本的 findAll() 方法,也可以从您的服务中调用:
List<Club> clubs = clubRepository.findAll(specification, new NamedEntityGraph(EntityGraphType.FETCH, "Club.players"));
英文:
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"))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论