【问题标题】:Spring Boot JPA - OneToMany relationship causes infinite loopSpring Boot JPA - OneToMany 关系导致无限循环
【发布时间】:2016-01-18 23:43:12
【问题描述】:

我有两个具有简单@OneToMany 关系的对象,如下所示:

父母:

@Entity
public class ParentAccount {

  @Id
  @GeneratedValue
  private long id;
  private String name;

  @OneToMany(fetch = FetchType.EAGER, mappedBy = "parentAccount")
  private Set<LinkedAccount> linkedAccounts;

}

孩子:

@Entity
public class LinkedAccount {

  @Id
  @GeneratedValue
  private long id;

  @ManyToOne(optional = false)
  private ParentAccount parentAccount;

  private String name;

  // empty constructor for JPA
  public LinkedAccount() {
  }

}

我使用 Spring CrudRepository 来操作这些实体。然而,当调用ParentAccount parent = parentAccountRepository.findOne(id); 时,某种无限循环开始发生,并且休眠垃圾邮件遍布整个控制台:

Hibernate: select linkedacco0_.parent_account_id as parent_a6_1_0_, linkedacco0_.id as id1_0_0_, linkedacco0_.id as id1_0_1_, linkedacco0_.aws_id as aws_id2_0_1_, linkedacco0_.key_id as key_id3_0_1_, linkedacco0_.name as name4_0_1_, linkedacco0_.parent_account_id as parent_a6_0_1_, linkedacco0_.secret_key as secret_k5_0_1_ from linked_account linkedacco0_ where linkedacco0_.parent_account_id=?

我尝试将获取类型更改为 LAZY,但随后出现此错误:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.berrycloud.scheduler.model.ParentAccount.linkedAccounts, could not initialize proxy - no Session

(似乎它正在尝试在事务上下文之外进行延迟加载)。

这是我的 CRUD 存储库:

@Repository
public interface ParentAccountRepository extends CrudRepository<ParentAccount, Long> {
}

谁能告诉我如何解决这个问题?我更喜欢 EAGER fetch 的解决方案。感谢您的任何提示

编辑:这是我正在使用的架构

CREATE TABLE parent_account (
    id BIGINT auto_increment,
    name VARCHAR(80) null,
    PRIMARY KEY (`id`)
);

CREATE TABLE linked_account (
    id BIGINT auto_increment,
    parent_account_id BIGINT,
    name VARCHAR(80) null,
    FOREIGN KEY (`parent_account_id`) REFERENCES `parent_account` (`id`),
    PRIMARY KEY (`id`)
);

【问题讨论】:

  • 嗯,每个父帐户都有多个关联帐户。而已。我看不出这如何解决成树状图。深度应该是 1。
  • 您的映射或架构中没有错误...我不认为您的无限循环问题来自于此...您在这些对象中做了其他事情吗?也许是一些 JSON / Jackson 注释?

标签: java hibernate jpa spring-boot


【解决方案1】:

正如第一个答案所暗示的:

不要在 @Entity 类上使用 Lombok 的 @Data 注释。

原因: @Data 生成 hashcode()equals()toString() 使用生成的 getter 的方法。使用 getter 意味着即使属性标记为 FetchType=LAZY,当然也会获取新数据。

hibernate 在某处尝试使用toString() 记录数据,但它崩溃了。

【讨论】:

  • Lombok 确实是内存模型创建无限循环的原因,最终导致 StackOverflowError 在我的情况下。尽管如此,我发现 Lombok 的 getter 和 setter 一代太有用了,不能完全放弃它。因此,我只通过将我自己的 toString() 方法添加到刚刚返回 super.toString() 的类来禁用 Lombok 生成 toString() - 它解决了我的问题。
  • 您仍然可以使用 Lombok,但您的惰性获取字段除外。 @EqualsAndHashCode(exclude={"firstLazyField","secondLazyField"})@ToString(exclude={"firstLazyField","secondLazyField"})
  • 正如@Ena 所说,您确实可以使用排除项。请注意,在 lombok 中,您还可以在字段级别使用 @EqualsAndHashcode.Include。如果您这样做,请不要忘记删除类级别 @EqualsAndHashcode 或通过在类级别设置 @EqualsAndHashCode(onlyExplicitlyIncluded = true) 来告诉 lombok 仅包含明确包含的字段。 P.S.:除了@ToString 上的排除列表,您还可以在字段上添加@ToString.Exclude
【解决方案2】:

问题解决了。我在引用 ParentAccount 的 LinkedAccount 中使用了自定义 @toString 方法。我不知道这会导致任何问题,因此我的问题中没有包含 toString。

显然,这导致了延迟加载的无限循环,删除此引用解决了问题。

【讨论】:

    【解决方案3】:

    这样的东西行不通?

    @Entity
    public class Account {
    
        @Id
        @GeneratedValue
        private long id;
        private String name;
    
        @ManyToOne(cascade={CascadeType.ALL})
        @JoinColumn(name="manager_id")
        private Account manager;
    
        @OneToMany((fetch = FetchType.EAGER, mappedBy="manager")
        private Set<Account> linkedAccounts = new HashSet<Account>();
    
    }
    

    【讨论】:

    • 我必须编辑我的 sql 模式(已经存在)来添加 manager_id 列。此外,spring 有它自动执行此操作的方式,因此我想使用默认的 Spring JPA 方式来解决此问题
    • 那里的非spring jpa是什么?顺便说一句,你的代码对我有用,你的数据库数据有问题。检查两次。
    • 我编辑了我的问题并添加了我正在使用的架构。也许这有什么问题?
    • 另外:parentAccount 引用上的 @JoinColumn(name = "parent_account_id") 没有做任何事情(我认为 Springs 会自动注入它,所以它已经存在)
    • 我真的很想帮助你,但我无法重现。我有相同的架构,关联帐户中有两个条目,父帐户中有一个条目,查询时没有循环。
    【解决方案4】:

    由于 Jackson2HttpMessageConverter 定义不明确,我最近遇到了这个问题。

    我做了以下类似的事情。

    @Bean
    RestTemplate restTemplate(@Qualifier("halJacksonHttpMessageConverter")
                                      TypeConstrainedMappingJackson2HttpMessageConverter halConverter) {
        final RestTemplate template = new RestTemplateBuilder().build();
        halConverter.setSupportedMediaTypes(List.of(/* some media types */)); 
        final List<HttpMessageConverter<?>> converters = template.getMessageConverters();
        converters.add(halConverter);
        template.setMessageConverters(converters);
        return template;
    }
    

    这导致了一个问题,因为媒体类型没有包含所有默认值。将其更改为以下解决了我的问题。

    halConverter.setSupportedMediaTypes(
        new ImmutableList.Builder<MediaType>()
            .addAll(halConverter.getSupportedMediaTypes())
            .add(/* my custom media type */)
            .build()
    );
    

    【讨论】:

      【解决方案5】:

      正如user1819111 所说,来自Lombok 的@Data@EntityFetchType=LAZY 不兼容。我使用了Lombok.Data (@Data),但出现了这个错误。

      由于我不想创建所有 get/set,我只是将 Lombok @Setter@Getter 放在你的班级中,一切都会正常工作。

      @Setter
      @Getter
      @Entity
      @Table(name = "file")
      @SequenceGenerator(name = "File_Sequence", allocationSize=1, sequenceName = "file_id_seq")
      public class MyClass{
          @Id
          @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "File_Sequence")
          @Column(name = "id")
          private Long id;
      
          @Column(name = "name")
          private String name;
      
          @OneToMany(mappedBy = "file", cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
          private Set<Base2FileDetail> details = new HashSet<>();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-08-02
        • 1970-01-01
        • 2021-08-25
        • 2017-01-09
        • 2023-03-09
        • 1970-01-01
        • 2018-11-29
        • 1970-01-01
        相关资源
        最近更新 更多