Java 导致infinte循环的Hibernate映射

Java 导致infinte循环的Hibernate映射,java,spring-boot,hibernate,jpa,orm,Java,Spring Boot,Hibernate,Jpa,Orm,注意:在完全阅读之前,请不要将其标记为重复 案例:我有三个类,分别是User、Post和Tag 用户Post OneToMany双向 后标记多个 我想要的解决方案: 映射应该像调用getUserById一样工作,我应该获得帖子 与用户相关,与帖子相关的标签 与POST和标记一样,如果调用getPostById,则应获得 用户和标签,如果我调用getTagByName,我应该会得到所有帖子 与标签相关 我尝试过的解决方案: @JsonMappedReference,@JsonBackReferen

注意:在完全阅读之前,请不要将其标记为重复

案例:我有三个类,分别是User、Post和Tag

用户Post OneToMany双向

后标记多个

我想要的解决方案:

映射应该像调用getUserById一样工作,我应该获得帖子 与用户相关,与帖子相关的标签

与POST和标记一样,如果调用getPostById,则应获得 用户和标签,如果我调用getTagByName,我应该会得到所有帖子 与标签相关

我尝试过的解决方案:

@JsonMappedReference,@JsonBackReference-用于读取操作,但用于创建/写入失败

@JsonIdentityInfo-不起作用

@JsonIgnore-工作正常,但我不想忽视,因为我没有得到上面提到的理想解决方案

@ToString.Exclude、@EqualsAndHashCode.Exclude-不起作用

也尝试了我自己的getter和setter以及@ToString方法,但也没有奏效

这是一个springboot项目

这是我的课

User.java

Post.java

Tag.java


因此,对于上面的类,我遇到了无限循环问题,如果我使用getUserById post对象,则user对象显示无法计算表达式方法抛出的“org.hibernate.LazyInitializationException”异常。如果在post对象中调用getAllPosts或getAllTags tags对象显示相同的错误,或者反之亦然,则很可能存在多个映射或获取实体的方式问题。让我们先尝试更改映射。正如您在评论中所说的,您希望使用双向映射,当然您可以自由使用它,但是通过使用mappedBy向hibernate显示谁是关系的所有者

如果是标签,则:

综上所述:

显示hibernate谁是关系的所有者 将FetchType.LAZY用于@manytone 为@ManyToMany使用Set。对于列表,当删除实体时,hibernate将删除所有实体,并重新添加未删除的实体。 向用户和标记添加同步添加和删除方法。如果使用双向映射,则必须维护关系的双方。 考虑添加CasCADEType。所有在@ OneToMany方面以及孤儿删除=真
您可以使用data transfer object来swow您需要的字段,也许您可以将json中的用户数组替换为这样的用户ID数组

@Transactional
public PostDto savePost(Post post) {
    Post save = postRepository.save(post);
    return Optional.of(save).map(this::transformPostEntityToDto).orElse(null);
}

@Override
public List<PostDto> getAllPosts() {
    return postRepository.findAll().stream()
            .map(this::transformPostEntityToDto).collect(Collectors.toList());
}

private PostDto transformPostEntityToDto(Post post) {
    return PostDto.builder()
            .id(post.getId())
            .createdAt(post.getCreatedAt())
            .postContent(post.getPostContent())
            .postStatus(post.getPostStatus())
            .postTitle(post.getPostTitle())
            .tags(Objects.nonNull(post.getTags())
                    ? post.getTags().stream().map(this::transformTagEntityToDto).collect(Collectors.toList())
                    : Collections.emptyList())
            .updatedAt(post.getUpdatedAt())
            .user(Optional.ofNullable(post.getUser()).map(this::transformUserEntityToDto).orElse(null))
            .build();
}

private TagDto transformTagEntityToDto(Tag tag) {
    return TagDto.builder()
            .id(tag.getId())
            .createdAt(tag.getCreatedAt())
            .tagName(tag.getTagName())
            .updatedAt(tag.getUpdatedAt())
            .postsIds(Objects.nonNull(tag.getPosts()) ? tag.getPosts().stream().map(Post::getId).collect(Collectors.toList())
                    : null)
            .build();
}

private UserDto transformUserEntityToDto(User user) {
    return UserDto.builder()
            .createdAt(user.getCreatedAt())
            .firstName(user.getFirstName())
            .id(user.getId())
            .lastName(user.getLastName())
            .password(user.getPassword())
            .updatedAt(user.getUpdatedAt())
            .userName(user.getUserName())
            .postsIds(Objects.nonNull(user.getPosts()) ? user.getPosts().stream().map(Post::getId).collect(Collectors.toList())
                    : null)
            .build();
}
它是灵活的,但是对于视图需要几个dto类

关于LazyInitializationException,它与“fetch”模式有关,与Jackson序列化无关。看看这个问题:。解决方案是将加载设置为eager:,或获取联接:。如果您使用的hibernate没有JPA抽象,您还可以查看以下内容:

关于使用jackson时的无限递归问题,请看:@JsonManagedReference和@JsonBackReference是一个不错的选择,但是您不能使用相同的类来获取用户的帖子,也不能使用相同的类来获取用户的帖子。序列化post时必须忽略用户,或者序列化用户时必须忽略post。否则你总是得到无限循环。解决方案是使用DTOs数据传输对象


另一件重要的事情是,在JPA中使用双向映射时,您必须自己将引用设置为集合的所有者。在这种情况下,向用户添加post时,请确保还将post对象上的用户引用设置为该用户。在这里,您可以看到在第22行的类Post中,您如何为Post属性设置当前添加的Post COMMENT对象上的引用,我在@OneToMany和@ManyToOne关系方面也遇到了类似的问题。我只想解释一下我修复代码的方法。希望这会有所不同

将@JsonBackReference添加到您的用户类中,这将解决您的循环问题。还要从所有类中删除cascade={CascadeType.PERSIST、CascadeType.MERGE、CascadeType.DETACH、CascadeType.REFRESH}行。级联是我无法执行更新方法的原因

请尝试我在下面提供的代码。在测试后输出流时,您应该能够将用户视为后输出流的一部分。此外,您应该能够列出用户,而不会遇到循环问题。可悲的是,我不太确定多对多的关系,因为我没有这方面的经验

User.java

Post.java

Tag.java


让我们一步一步来

由于您使用的是Spring Boot,我怀疑这些实体是直接从REST控制器返回的。因此,当您尝试返回用户并调用getUserById时,它会执行以下操作:

Hibernate通过id获取用户并设置帖子的延迟集合 Spring试图使用Jackson创建该用户的JSON,Jackson调用所有getter 由于帖子尚未加载,hibernate也将 A.如果ses 锡安仍然开放 B如果会话已关闭,则抛出LazyInitializationException 因此,你的家庭作业任务1是,如果你真的需要,确保课程仍然开放。默认情况下,在Spring启动应用程序中,Hibernate会话边界与Spring@Transactional边界相同。但是,如果您使用的是Spring Boot 2+,请查找属性Spring.jpa.open-in-view。默认情况下是这样的,它注册OpenEntityManagerViewInterceptor,在web请求的整个生命周期中为您提供打开的Hibernate会话

当会话打开且延迟加载工作时,将发生以下情况:

通过id将一个用户加载到会话中 当Jackson调用getter时,该用户的所有帖子都是延迟加载的 由于Jackson递归地调用所有getter,因此每个post.getTags都将被调用 现在将调用每个tag.getPosts 再次调用每个post.getUser和post.getTags ... 如您所见,您将把所有数据库加载到应用程序中,并将得到StackOverflowException:

所以你的家庭作业任务2是放回@JsonMappedReference,@JsonBackReference,例如,如果你为帖子加载了所有标签,那么你不应该为标签加载所有帖子

我必须指出,这不是正确的做法。最好先使用joinfetch加载所需的所有内容,然后开始构建JSON。但既然你问了…:


关于写操作,它有点棘手,这取决于您实际如何做。但至少要确保会话和事务在您尝试向数据库写入数据时处于打开状态。

我已经删除了所有映射,我可以获取用户,三个不同调用中的post和post标记及其工作正常我尝试了上面解释或显示的所有映射,但在读/写操作中出现错误,为了避免所有这些,我进行了更改,因此它没有任何映射

我认为用户、post和post以及标记中没有双向映射。在问题演示。@vivekdubey有双向映射我没有看到@MappedBy,所以我对双向映射有疑问。对不起,问题很混乱。标题为“无限循环”,内容询问LazyInitializationException,然后您提到您“遇到无限循环问题”-在哪里遇到它?您还说您尝试了@JsonIgnore、@ToString.Exclude、@EqualsAndHashCode.Exclude-这些与问题的关系到底如何?他们做了非常不同的事情,所以我不得不问:你想解决什么问题?我试过Set,FetchType.LAZY,但没有成功。你试过在许多映射的一侧添加mappedBy吗?你能用这些代码描述问题吗?我对它进行了测试,一般来说,对于这种类型的关系,每个实体都需要几个DTO,然后可以转换具有不同关系深度的对象

@Entity
@Table(name = "post")
@Data
public class Post {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private int id;

  @ManyToOne(
      cascade = {CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
  @JoinColumn(name = "user_id")
  private User user;

  @Column(name = "title")
  private String postTitle;

  @Column(name = "content")
  private String postContent;

  @Column(name = "status")
  private String postStatus;

  @Column(name = "created_at")
  @CreationTimestamp
  private Timestamp createdAt;

  @Column(name = "updated_at")
  @UpdateTimestamp
  private Timestamp updatedAt;

  
  @ManyToMany(
      cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
  @JoinTable(
      name = "post_tag",
      joinColumns = @JoinColumn(name = "post_id"),
      inverseJoinColumns = @JoinColumn(name = "tag_id"))
  private List<Tag> tags;


@Entity
@Table(name = "tag")
@Data
public class Tag {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private int id;

  @Column(name = "name")
  private String tagName;

  @Column(name = "created_at")
  @CreationTimestamp
  private Timestamp createdAt;

  @Column(name = "updated_at")
  @UpdateTimestamp
  private Timestamp updatedAt;

  @ManyToMany(
      cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
  @JoinTable(
      name = "post_tag",
      joinColumns = @JoinColumn(name = "tag_id"),
      inverseJoinColumns = @JoinColumn(name = "post_id"))
  private List<Post> posts;

@Entity
@Table(name = "tag")
@Data
public class Tag {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private int id; // I'd use object instead of primitive
  
  // other columns  

  @ManyToMany(
      cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
  @JoinTable(
      name = "post_tag",
      joinColumns = @JoinColumn(name = "tag_id"),
      inverseJoinColumns = @JoinColumn(name = "post_id"))
  private Set<Post> posts = new HashSet<>(); // use set it is much more efficient, also worth to initialize

  // getters, setters, add and remove synchronization methods
}
@Entity
@Table(name = "post")
@Data
public class Post {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private int id;

  @ManyToOne(
      cascade = {CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH}, fetch = FetchType.LAZY) // by default EAGER, not efficient 
  @JoinColumn(name = "user_id")
  private User user;

  // other columns 

  @ManyToMany(mappedBy = "posts",
      cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH})
  private Set<Tag> tags = new HashSet<>();

  // getters, setters
}
@Transactional
public PostDto savePost(Post post) {
    Post save = postRepository.save(post);
    return Optional.of(save).map(this::transformPostEntityToDto).orElse(null);
}

@Override
public List<PostDto> getAllPosts() {
    return postRepository.findAll().stream()
            .map(this::transformPostEntityToDto).collect(Collectors.toList());
}

private PostDto transformPostEntityToDto(Post post) {
    return PostDto.builder()
            .id(post.getId())
            .createdAt(post.getCreatedAt())
            .postContent(post.getPostContent())
            .postStatus(post.getPostStatus())
            .postTitle(post.getPostTitle())
            .tags(Objects.nonNull(post.getTags())
                    ? post.getTags().stream().map(this::transformTagEntityToDto).collect(Collectors.toList())
                    : Collections.emptyList())
            .updatedAt(post.getUpdatedAt())
            .user(Optional.ofNullable(post.getUser()).map(this::transformUserEntityToDto).orElse(null))
            .build();
}

private TagDto transformTagEntityToDto(Tag tag) {
    return TagDto.builder()
            .id(tag.getId())
            .createdAt(tag.getCreatedAt())
            .tagName(tag.getTagName())
            .updatedAt(tag.getUpdatedAt())
            .postsIds(Objects.nonNull(tag.getPosts()) ? tag.getPosts().stream().map(Post::getId).collect(Collectors.toList())
                    : null)
            .build();
}

private UserDto transformUserEntityToDto(User user) {
    return UserDto.builder()
            .createdAt(user.getCreatedAt())
            .firstName(user.getFirstName())
            .id(user.getId())
            .lastName(user.getLastName())
            .password(user.getPassword())
            .updatedAt(user.getUpdatedAt())
            .userName(user.getUserName())
            .postsIds(Objects.nonNull(user.getPosts()) ? user.getPosts().stream().map(Post::getId).collect(Collectors.toList())
                    : null)
            .build();
}
@Entity
@Table(name = "user")
@Data
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private int id;

  @Column(name = "username")
  private String userName;

  @Column(name = "password")
  private String password;

  @Column(name = "first_name")
  private String firstName;

  @Column(name = "last_name")
  private String lastName;

  @Column(name = "created_at")
  @CreationTimestamp
  private Timestamp createdAt;

  @Column(name = "updated_at")
  @UpdateTimestamp
  private Timestamp updatedAt;

  @JsonBackReference
  @OneToMany(mappedBy = "user")
  private List<Post> posts;
@Entity
@Table(name = "post")
@Data
public class Post {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private int id;

  @ManyToOne
  @JoinColumn(name = "user_id")
  private User user;

  @Column(name = "title")
  private String postTitle;

  @Column(name = "content")
  private String postContent;

  @Column(name = "status")
  private String postStatus;

  @Column(name = "created_at")
  @CreationTimestamp
  private Timestamp createdAt;

  @Column(name = "updated_at")
  @UpdateTimestamp
  private Timestamp updatedAt;

  
  @ManyToMany
  @JoinColumn(name = "tag_id")
  private List<Tag> tags;
@Entity
@Table(name = "tag")
@Data
public class Tag {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private int id;

  @Column(name = "name")
  private String tagName;

  @Column(name = "created_at")
  @CreationTimestamp
  private Timestamp createdAt;

  @Column(name = "updated_at")
  @UpdateTimestamp
  private Timestamp updatedAt;

  @ManyToMany(mappedBy = "tags"))
  private List<Post> posts;