【问题标题】:Getting nested entity data resulting in stackoverflow error获取嵌套实体数据导致 stackoverflow 错误
【发布时间】:2019-12-18 01:02:55
【问题描述】:

我在 2 个表 User 和 Joke 之间有多对多的关系。它们由一个连接表 fav_joke 连接,该连接表包含这些表中的 id 值。根据我的设置方式,当我从这些表中插入和删除时,它似乎可以正常工作。

问题是当我尝试查询我的用户表以获取所有相关的笑话时。再次工作正常,但以某种初始形式重复它,最终导致 StackOverflow 错误。请参阅图像以查看响应。如您所见,我在笑话中得到了一个用户,该用户又拥有自己的笑话,该笑话拥有自己的用户,并且一直在继续……

当我进行此查询时,我只期望用户下的相关笑话,在这种情况下应该是 2,仅此而已。我认为这与我在 Joke Entity 中使用 @ManyToMany 关系注释有关,但我相信我需要它。请指教。谢谢。

实体

@Getter
@Setter
@NoArgsConstructor
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    long memberId;

    @ManyToMany
    @JoinTable(
        name = "fav_joke",
        joinColumns = @JoinColumn(name = "memberId"),
        inverseJoinColumns = @JoinColumn(name = "id")
    )
    private Set<Joke> jokes;
}  

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "joke")
public class Joke {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String question;
    private String answer;
    private boolean isFav;

    @ManyToMany(mappedBy = "jokes")
    private Set<User> users;
}

存储库

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User getUserByMemberId(long memberId);
}

@Repository
public interface JokeRepository extends JpaRepository<Joke, Long> {
    @Query("select j from Joke j where j.id =:id")
    Joke getJokeById(long id);
}

Controller - 这个调用是抛出堆栈溢出的调用。

@GetMapping("/get/fav/member_id/{memberId}")
public Set<Joke> getAllFavJokes(@PathVariable long memberId){
    User user = userRepository.getUserByMemberId(memberId);
    return user.getJokes();
}

我认为这无关紧要。只是添加它以防万一。这样做会填充由于 ManyToMany 映射而动态创建的 fav_joke 表。

@GetMapping("/fav/joke_id/{id}/member_id/{memberId}/isFav/{isFav}")
    public String toggleFavJoke(@PathVariable long id, @PathVariable long memberId, @PathVariable boolean isFav){
        Joke joke = jokeRepository.getJokeById(id);
        User user = userRepository.findById(memberId).orElse(null);

        if(user != null && joke != null){
            if(user.getJokes() != null){
                if(isFav){
                    user.getJokes().remove(joke);
                }else {
                    user.getJokes().add(joke);
                }
            }else {
                if(!isFav){
                    user.setJokes(Stream.of(joke).collect(Collectors.toSet()));
                }
            }
            userRepository.save(user);
            return "Fav toggled successfully";
        }
        return "Unable to add fav. Invalid user or joke";
    }

目前表内信息如下:(笑话表有1k+行)

更新

按照 cmets 中的建议添加了 @@JsonIdentityInfo 注释。堆栈溢出问题现在已经消失,但附加信息的方式不正确。

这是我得到的 JSON 格式。

[{
    "id": 5,
    "question": "What is a gas station's favorite type of shoe?",
    "answer": "Pumps.",
    "users": [{
        "memberId": 1,
        "jokes": [5, {
            "id": 7,
            "question": "What kind of place should you never take a dog?",
            "answer": "To the Flea Market.",
            "users": [1],
            "fav": false
        }, {
            "id": 8,
            "question": "What do you call a cow in an earthquake?",
            "answer": "A milkshake!",
            "users": [1],
            "fav": false
        }, {
            "id": 6,
            "question": "Why was 6 afraid of 7?",
            "answer": "Because 7 8 9!",
            "users": [1],
            "fav": false
        }, {
            "id": 4,
            "question": "Why was Dracula put in jail?",
            "answer": "He tried to rob a blood bank.",
            "users": [1],
            "fav": false
        }]
    }],
    "fav": false
}, 7, 8, 6, 4]

奇怪的是,只有第一个笑话保持在顶部,其余的则不断附加到内部的用户列表中,如上图所示。

我期待以下内容(并计划排除内部用户数据)。

[
    {
        "id": 6,
        "question": "J1 What is a gas station's favorite type of shoe?",
        "answer": "Pumps.",
        "users": [{
            "memberId": 1,
            "jokes": [5]
        }],
        "fav": false
    },
    {
        "id": 6,
        "question": "J2 What is a gas station's favorite type of shoe?",
        "answer": "Pumps.",
        "users": [{
            "memberId": 1,
            "jokes": [6]
        }],
        "fav": false
    },
    {
        "id": 7,
        "question": "J3 What is a gas station's favorite type of shoe?",
        "answer": "Pumps.",
        "users": [{
            "memberId": 1,
            "jokes": [7]
        }],
        "fav": false
    }
] 

【问题讨论】:

  • 您可能正在寻找@JsonIdentityInfo。实体通常应该被引用(包含在 ID 中)而不是嵌入到响应中。 (根据 REST 约定,您的路径没有多大意义,但这是另一回事。)
  • @chrylis 我将尝试使用该注释。我知道它是题外话,但我可以知道路径的问题吗?
  • 首先,看起来“最喜欢的笑话”可能属于“成员”作为第一级层次结构,所以它更像/members/{memberId}/...。接下来,“成为最爱”是存在或不存在关系,所以我期待/members/{memberId}/favorites;发布一个带有笑话 ID 的请求 /members/{memberId}/favorites 添加一个,DELETE /members/{memberId}/favorites/{jokeId} 删除它。
  • @chrylis 感谢您的提示。该注释有效。没有 stackoverflow 错误并得到正确的响应。担心的是,当我调试时,我仍然可以看到嵌套结果..这是一个问题并导致我的资源出现问题..
  • 没有。调试器只是正确地向您显示循环引用,除非您一直单击它,否则这不是问题。

标签: java spring-boot jpa jpql


【解决方案1】:

您可以使用@JsonIgnoreProperties 来打破循环:

public class Joke {

    @JsonIgnoreProperties("jokes")
    public Set<User> getUsers(){
        return this.users;
    }    
}  

如果你得到 User 可能会发生同样的问题,所以你可能还需要打破来自 User 的循环:

public class User {

    @JsonIgnoreProperties("users")
    public Set<Joke> getJokes(){
        return this.jokes;
    }    
}  

【讨论】:

  • 通常最好使用@JsonIdentityInfo 映射ID。
猜你喜欢
  • 1970-01-01
  • 2015-04-06
  • 2012-02-08
  • 1970-01-01
  • 2013-11-29
  • 1970-01-01
  • 2019-09-14
  • 2015-09-12
  • 2011-12-19
相关资源
最近更新 更多