繁体   English   中英

在 JSON 响应中返回延迟加载的实体

[英]Returning a lazy loaded entity in the JSON response

我的Club实体有问题 - 我正在使用LAZY获取类型和ModelMapper返回我的 JSON。问题是,如果我使用LAZY而不是EAGER ,我得到的响应是GET /api/players/{id}是:

Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: could not initialize proxy

以及来自 Postman 的屏幕截图:

在此处输入图像描述

当我调试控制器的动作时:

@GetMapping("/api/players/{id}")
    ResponseEntity<PlayerDto> getPlayer(@PathVariable String id) {
        Player foundPlayer = playerInterface.getPlayer(Long.valueOf(id));
        PlayerDto playerToDto = convertToDto(foundPlayer);

        return ResponseEntity.ok().body(playerToDto);
    }

...

 private PlayerDto convertToDto(Player player) {
        return modelMapper.map(player, PlayerDto.class);
    }

似乎foundPlayerplayerToDto都有这样的Club

在此处输入图像描述

但是当我执行foundPlayer.getClub().getName()时,我得到了一个正确的名称。 我知道这可能是预期的行为,但我希望Club像这样在我的回复中返回(如果设置了EAGER ,则为回复的屏幕截图): 在此处输入图像描述

无需将获取类型设置为EAGER

我的Player实体:

@Entity
public class Player {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;


    private String firstName;


    private String lastName;;

    @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.EAGER)
    @JsonManagedReference
    private Club club;

我的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;

来自PlayerServicegetPlayer方法(controller 调用的那个方法):

 @Override
    public Player getPlayer(Long id) {
        Optional<Player> foundPlayer = playerRepository.findById(id);
        return foundPlayer.orElseThrow(PlayerNotFoundException::new);
    }

PlayerToDto :

package pl.ug.kchelstowski.ap.lab06.dto;

import pl.ug.kchelstowski.ap.lab06.domain.Club;

public class PlayerDto {
    private Long id;
    private String firstName;
    private String lastName;

    private Club club;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Club getClub() {
        return club;
    }

    public void setClub(Club club) {
        this.club = club;
    }



}

没错,这是延迟加载的预期行为。 这是一件好事,不要将其设置为 eager,而不是直接在您的响应主体上返回一个 Club @Entity class。 您应该创建一个 ClubDto 并使用另一个 convertToDto 方法对其进行初始化。 这有点乏味(我喜欢使用MapstructLombok来缓解这种情况),但它会诱导 Hibernate 进行您需要的所有查询。

@Data
public class ClubDto {
    private String id;
    private String name;
}
@Mapper
public interface ClubMapper {
    public ClubDTO mapToDto(Club club);
}

糟糕,没有意识到您已经在使用ModelMapper 我对此不太熟悉,但听起来如果您将 Club 换成 ClubDto 就可以了。

伙计们,我有一个解决方案,但我想听听您是否可以通过这种方式完成,或者它是某种反模式。 我只是简单地将playerToDto的 Club 设置为带有foundPlayer ID 的全新获取的Club

@GetMapping("/api/players/{id}")
    ResponseEntity<PlayerDto> getPlayer(@PathVariable String id) {
        Player foundPlayer = playerInterface.getPlayer(Long.valueOf(id));
        PlayerDto playerToDto = convertToDto(foundPlayer);
        playerToDto.setClub(clubInterface.getClub(foundPlayer.getClub().getId()));

        return ResponseEntity.ok().body(playerToDto);
    }

最后我想到了这个:

@GetMapping("/api/players")
    ResponseEntity<List<PlayerDto>> getAllPlayers() {
        List<PlayerDto> playersList = playerInterface.getAllPlayers().stream().map(this::convertToDto).collect(Collectors.toList());
        playersList.forEach(playerInterface::fetchClubToPlayer);
        return ResponseEntity.ok().body(playersList);
    }

    @GetMapping("/api/players/{id}")
    ResponseEntity<PlayerDto> getPlayer(@PathVariable String id) {
        Player foundPlayer = playerInterface.getPlayer(Long.valueOf(id));
        PlayerDto playerToDto = convertToDto(foundPlayer);
        playerInterface.fetchClubToPlayer(playerToDto);
        return ResponseEntity.ok().body(playerToDto);
    }
public PlayerDto fetchClubToPlayer(PlayerDto player) {
        if (player.getClub() != null) {
            Club club = clubInterface.getClub(player.getClub().getId());
            player.setClub(club);
        }
        return player;
    }

还好吗?

我建议您使用@EntityGraph来配置结果方法查询的获取计划。 例如,您可以在PlayerRepository中声明一个方法来通过 id 查找Player实体,除了默认的findById方法,它的Club实体将被急切地获取。

public interface PlayerRepository extends JpaRepository<Player, Long>{
   ...
   @EntityGraph(attributePaths = {"club"})
   Optional<Player> findWithClubFetchedEagerlyById(LongId);
}

通过提供attributePaths ,定义了应该急切获取的字段。

如果在调用findById方法时总是急切地获取Club实体,则不需要单独的方法,因此您可以使用@EntityGraph注释默认方法。

有了这个解决方案,.network 遍历被最小化,因为所有需要的数据都是从数据库中一次获取的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM