Spring Data JPA - 无限递归的双向关系 [英] Spring Data JPA - bidirectional relation with infinite recursion

查看:86
本文介绍了Spring Data JPA - 无限递归的双向关系的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,这是我的实体.

玩家:

@Entity@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,属性=id")公共课播放器{//其他字段@ManyToOne@JoinColumn(name = "pla_fk_n_teamId")私人团队;//方法}

团队:

@Entity@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,属性=id")公开课团队{//其他字段@OneToMany(mappedBy = "团队")私人列表<玩家>会员;//方法}

正如许多主题已经说明的那样,您可以通过 Jackson 以多种方式避免 WebService 中的 StackOverflowExeption.

这很酷,但 JPA 仍然在序列化之前构造一个具有无限递归到另一个实体的实体.这很丑陋,因为请求需要更长的时间.检查此屏幕截图:IntelliJ 调试器

有办法解决吗?知道我想要不同的结果取决于端点.示例:

  • 端点 /teams/{id} => Team={id..., members=[Player={id..., team=null}]}
  • 端点/members/{id} => Player={id..., team={id..., members=null}}

谢谢!

也许这个问题不是很清楚,给出了我得到的答案,所以我会尽量更准确.

我知道可以通过 Jackson(@JSONIgnore、@JsonManagedReference/@JSONBackReference 等)或通过映射到 DTO 来防止无限递归.我仍然看到的问题是:以上都是查询后处理.Spring JPA 返回的对象仍然是(例如)一个 Team,包含玩家列表,包含一个团队,包含一个玩家列表等.

我想知道是否有办法告诉 JPA 或存储库(或任何东西)不要一遍又一遍地绑定实体内的实体?

解决方案

这是我在项目中处理这个问题的方法.

我使用了数据传输对象的概念,在两个版本中实现:完整对象和轻对象.

我将一个包含引用实体的对象定义为 Dto(仅保存可序列化值的数据传输对象),并将一个没有引用实体的对象定义为 Info.

Info 对象只保存关于实体本身的信息,而不保存关于关系的信息.

现在,当我通过 REST API 提供 Dto 对象时,我只需将 Info 对象放在引用中.

假设我通过 GET/players/1 传递 PlayerDto :

公共类 PlayerDto{私人字符串播放器名称;私人字符串玩家国家;私人团队信息;}

TeamInfo 对象看起来像

public class TeamInfo {私人字符串团队名称;私人字符串团队颜色;}

TeamDto

相比

public class TeamDto{私人字符串团队名称;私人字符串团队颜色;私人列表<PlayerInfo>球员;}

这避免了无休止的序列化,并且还为您的其余资源做出了合乎逻辑的结束,否则您应该能够GET/player/1/team/player/1/team

此外,该概念清楚地将数据层与客户端层(在本例中为 REST API)分开,因为您没有将实际的实体对象传递给接口.为此,您将服务层内的实际实体转换为 DtoInfo.为此,我使用 http://modelmapper.org/,因为它非常简单(一个简短的方法调用).>

我还懒惰地获取所有引用的实体.我的服务方法获取实体并将其转换为 Dto 以在事务范围内运行,这无论如何都是很好的做法.

懒加载

要告诉 JPA 延迟获取实体,只需通过定义获取类型来修改关系注释.其默认值是 fetch = FetchType.EAGER,这在您的情况下是有问题的.这就是为什么您应该将其更改为 fetch = FetchType.LAZY

public class TeamEntity {@OneToMany(mappedBy = "team",fetch = FetchType.LAZY)私人列表<PlayerEntity>会员;}

同样的播放器

public class PlayerEntity {@ManyToOne(fetch = FetchType.LAZY)@JoinColumn(name = "pla_fk_n_teamId")私人团队实体团队;}

从服务层调用存储库方法时,重要的是,这是在 @Transactional 范围内发生的,否则,您将无法获得延迟引用的实体.看起来像这样:

 @Transactional(readOnly = true)public TeamDto getTeamByName(String teamName){TeamEntity entity= teamRepository.getTeamByName(teamName);返回modelMapper.map(entity,TeamDto.class);}

First, here are my entities.

Player :

@Entity
@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class, 
property="id")
public class Player {

    // other fields

    @ManyToOne
    @JoinColumn(name = "pla_fk_n_teamId")
    private Team team;

    // methods

}

Team :

@Entity
@JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class, 
property="id")
public class Team {

    // other fields

    @OneToMany(mappedBy = "team")
    private List<Player> members;

    // methods

}

As many topics already stated, you can avoid the StackOverflowExeption in your WebService in many ways with Jackson.

That's cool and all but JPA still constructs an entity with infinite recursion to another entity before the serialization. This is just ugly ans the request takes much longer. Check this screenshot : IntelliJ debugger

Is there a way to fix it ? Knowing that I want different results depending on the endpoint. Examples :

  • endpoint /teams/{id} => Team={id..., members=[Player={id..., team=null}]}
  • endpoint /members/{id} => Player={id..., team={id..., members=null}}

Thank you!

EDIT : maybe the question isn't very clear giving the answers I get so I'll try to be more precise.

I know that it is possible to prevent the infinite recursion either with Jackson (@JSONIgnore, @JsonManagedReference/@JSONBackReference etc.) or by doing some mapping into DTO. The problem I still see is this : both of the above are post-query processing. The object that Spring JPA returns will still be (for example) a Team, containing a list of players, containing a team, containing a list of players, etc. etc.

I would like to know if there is a way to tell JPA or the repository (or anything) to not bind entities within entities over and over again?

解决方案

Here is how I handle this problem in my projects.

I used the concept of data transfer objects, implemented in two version: a full object and a light object.

I define a object containing the referenced entities as List as Dto (data transfer object that only holds serializable values) and I define a object without the referenced entities as Info.

A Info object only hold information about the very entity itself and not about relations.

Now when I deliver a Dto object over a REST API, I simply put Info objects for the references.

Let's assume I deliever a PlayerDto over GET /players/1:

public class PlayerDto{
   private String playerName;
   private String playercountry;
   private TeamInfo;
}

Whereas the TeamInfo object looks like

public class TeamInfo {
    private String teamName;
    private String teamColor;
}

compared to a TeamDto

public class TeamDto{
    private String teamName;
    private String teamColor;
    private List<PlayerInfo> players;
}

This avoids an endless serialization and also makes a logical end for your rest resources as other wise you should be able to GET /player/1/team/player/1/team

Additionally, the concept clearly separates the data layer from the client layer (in this case the REST API), as you don't pass the actually entity object to the interface. For this, you convert the actual entity inside your service layer to a Dto or Info. I use http://modelmapper.org/ for this, as it's super easy (one short method call).

Also I fetch all referenced entities lazily. My service method which gets the entity and converts it to the Dto there for runs inside of a transaction scope, which is good practice anyway.

Lazy fetching

To tell JPA to fetch a entity lazily, simply modify your relationship annotation by defining the fetch type. The default value for this is fetch = FetchType.EAGER which in your situation is problematic. That is why you should change it to fetch = FetchType.LAZY

public class TeamEntity {

    @OneToMany(mappedBy = "team",fetch = FetchType.LAZY)
    private List<PlayerEntity> members;
}

Likewise the Player

public class PlayerEntity {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "pla_fk_n_teamId")
    private TeamEntity team;
}

When calling your repository method from your service layer, it is important, that this is happening within a @Transactional scope, otherwise, you won't be able to get the lazily referenced entity. Which would look like this:

 @Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
    TeamEntity entity= teamRepository.getTeamByName(teamName);
    return modelMapper.map(entity,TeamDto.class);
}

这篇关于Spring Data JPA - 无限递归的双向关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆