英文:
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
@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.
答案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
JpaSpecificationExecutor
and 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
EntityManager
as 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"))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论