OneToMany需要集合数据时的延迟初始化

huangapple go评论96阅读模式
英文:

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 = &quot;club&quot;, cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
    @JsonBackReference
    private List&lt;Player&gt; 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&lt;Club&gt; findAll();

    @Query(&quot;Select clubs from Club clubs left join fetch clubs.players&quot;)
    List&lt;Club&gt; 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

  1. @Transactional方法中访问所有的惰性字段。您没有展示您的代码,但通常有一个负责进行@Transactional操作的服务门面层。它会调用存储库。
  2. 编写一个查询,获取所有所需的数据。然后,您需要创建一个专门用于获取所有与该逻辑所需的惰性字段的方法。
  3. 使用OpenSessionInViewFilter或OpenSessionInViewInterceptor,以便在执行到达控制器之前启动Session/EntityManager。然后,在请求处理结束时,同一高级层将关闭该会话。
英文:

There are 3 choices:

  1. 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.
  2. 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.
  3. 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查询之外,您可以执行以下之一:

  1. 使您的存储库扩展JpaSpecificationExecutor,并以编程方式描述需要获取的内容,如此答案中所述。

  2. 使用实体图,可以使用注释或以编程方式描述,然后使用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 .:

  1. Make your repository extend JpaSpecificationExecutor and programmatically describe what needs to be fetched as described in this answer

  2. 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 = &quot;Club.players&quot;,
    attributeNodes = @NamedAttributeNode(&quot;players&quot;)
)
public class Club {

Then you should annotate your findAll() method with this graph using it's name

@EntityGraph(value = &quot;Club.players&quot;)
List&lt;Club&gt; 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/&lt;version&gt;

Then replace your repository with

@Repository
public interface ClubRepository extends JpaSpecificationExecutor&lt;Club&gt;, JpaRepository&lt;Club, Long&gt;, EntityGraphJpaSpecificationExecutor&lt;Club&gt; {

}

And then you'll have basic method findAll() and also from your sevice you can call

List&lt;Club&gt; clubs = clubRepository.findAll(specification, new NamedEntityGraph(EntityGraphType.FETCH, &quot;Club.players&quot;))

huangapple
  • 本文由 发表于 2023年1月9日 05:23:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/75051411.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定