【问题标题】:JPA starts to consume more and more memory after each iterationJPA 在每次迭代后开始消耗越来越多的内存
【发布时间】:2020-10-08 10:33:02
【问题描述】:

目前我尝试在 JPA 的帮助下从 web api 存储一些新闻。 我需要存储 3 个实体:网页、NewsPost 和返回新闻帖子的查询。我为三者中的每一个准备了一张桌子。我的简化 JPA 实体如下所示:

@Entity
@Data
@Table(name = "NewsPosts", schema = "data")
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class NewsPost {

    @Id
    @Column(name = "id")
    private long id;
    @Basic
    @Column(name = "subject")
    private String subject;
    @Basic
    @Column(name = "post_text")
    private String postText;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
    @JoinColumn(name = "newsSite")
    private NewsSite site;

    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.MERGE)
    @JoinTable(name = "query_news_post", joinColumns = @JoinColumn(name = "newsid"), inverseJoinColumns = @JoinColumn(name = "queryid"))
    private Set<QueryEntity> queries;
}


@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "queries", schema = "data")
@EqualsAndHashCode
public class QueryEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;
    @EqualsAndHashCode.Exclude
    @Basic
    @Column(name = "query")
    private String query;

    // needs to be exclueded otherwise we can create stack overflow, because of circular references...
    @EqualsAndHashCode.Exclude
    @ToString.Exclude
    @ManyToMany(mappedBy = "queries", fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
    Set<PostsEntity> posts;

}



@Entity
@Data
@Table(name = "sites", schema = "data")
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class newsSite {
    @Id
    @Column(name = "SiteId")
    private long id;
    @Basic
    @Column(name = "SiteName")
    private String site;

}

目前我正在执行以下操作:创建查询并检索查询。然后我开始爬行: 我以分页方式从 web api 获取对象,页面大小为 100 个新闻帖子我使用对象映射器将 json 响应映射到我的实体类。

之后我尝试了两种不同的方法:

  1. 我将查询 ID 作为设置添加到 NewsPost 并使用 EntityManager 的合并选项将其写回 DB。这工作得很好,直到我说到点子上,我再次得到一个 NewsPost 以进行另一个查询,然后新的查询被旧的查询覆盖。为了解决这个问题,它尝试了 2。
  2. 我检查 NewsPost 是否已经存在,如果它确实存在,我检索到帖子,将新查询添加到现有查询中,然后像以前一样将其合并回数据库。当这样做时,我工作得很好,我得到了第一批的预期结果,但是突然应用程序开始为第三批消耗越来越多的内存。我附上了 JavaVisualVM 的截图。有人知道为什么会这样吗?

编辑: 由于在 cmets 中提出了一些问题,我想在这里提供问题的答案。

我认为爬行一切正常。 Webapi 以 json 的形式返回。我正在使用杰克逊映射器将其映射到 POJO,然后我使用推土机映射器将 POJO 转换为实体。 (是的,我需要先执行 POJO 的步骤,以用于应用程序中的其他目的,这可以正常工作)。

关于 EntityManager 的写作,我不确定我是否正确。

起初,我创建了一个 JPA 存储库来检查帖子是否已经存在(以获取旧的查询 ID 并避免在 queryid、postid 表中覆盖的问题)。我的 JPA 存储库如下所示。

@Repository
public interface PostRepo extends JpaRepository<NewsPost, Long> {

    NewsPost getById(long id);
}

为了更新帖子,我这样做如下:

private void updatePosts(List<NewsPost> posts){
    posts.forEach(post->{
                NewsPost foundPost = postRepo.getById(post.getId());
                if(foundPost!=null){
                    post.getQueries().addAll(foundPost.getQueries());   
                }});
}

我目前正在编写我的实体,如下所示,我有一个实体列表,其中还包含更新的帖子,并且我的班级中有一个自动连接的 EntityManagerFactory 来处理写作。

EntityManager em = entityManagerFactory.createEntityManager();
        try {
            EntityTransaction transaction = em.getTransaction();
            transaction.begin();
            entities.forEach(entity->em.merge(entity))
            em.flush();
            transaction.commit();
        } finally {
            em.clear();
            em.close();
        }

我很确定这是写作过程。如果我保持软件的逻辑相同,但只跳过合并,或者只是打印或将实体转储到文件中,那么一切正常且快速且没有出现错误,那么这似乎是合并注释的问题?

关于我的程序是否因为内存消耗而死的问题,这取决于它。如果我在我的 Mac 上运行它会消耗多达 8 GB 以上的内存,但 MAC OS 正在处理这个并将内存交换到磁盘。如果我将其作为 docker 容器 von CentOS 运行,则该进程会因内存不足而被终止。

如果这是相关的,现在不要,但我使用的是 OpenJDK 11、Springboot 2.2.6 和 MYSQL 8 数据库。

我在 application.yml 中配置 jpa 如下:

spring:
  main:
    allow-bean-definition-overriding: true
  datasource:
    url: "jdbc:mysql://db"
    username: user
    password: secret
    driver-class-name: com.mysql.cj.jdbc.Driver
    test-while-idle: true
    validation-query: Select 1
  jpa:
    database-platform: org.hibernate.dialect.MySQL8Dialect
    hibernate:
      ddl-auto: none
    properties:
      hibernate:
        event:
          merge:
            entity_copy_observer: allow
    ```

【问题讨论】:

  • 能否请您发布执行所有“抓取”和保存的实际代码?
  • 确保在您发布的代码中包含事务边界。还要确保您了解 JPA 实体生命周期。如果您不这样做,则很可能是您的问题的原因。
  • 它真的会耗尽内存吗?还是它只是消耗更多内存,但运行良好?
  • 感谢所有 cmets。我尝试添加额外的请求信息。
  • 您的模型有点难以理解:您在开始时提到了您的三个实体。您的三个实体的实现虽然链接到其他一些实体。忽略拼写错误,我发现NewsPostNewsSiteQueryEntitySidSocialgistQueryEntitySocialgistBoardPostsEntity。能否请您描述一下您的模型或更正它?

标签: java jpa spring-data-jpa


【解决方案1】:

如果合并过程有问题,可以在每次合并后添加em.flush();em.clear(); 以快速解决entityManager 中的内存消耗低:

EntityTransaction transaction = em.getTransaction();
transaction.begin();
entities.forEach(entity-> {
    em.merge(entity);
    em.flush();
    em.clear();
});
transaction.commit();

但是,我认为您应该更改模型。加载每个帖子的所有现有查询只是为了添加新查询是非常低效的。您可以将 N-M 关系建模为一个新实体并保持新关系。

【讨论】:

    【解决方案2】:

    我自己尝试解决了这个问题。我为多对多关系创建了一个实体。之后,我为每个实体创建了 CRUD 存储库,并使用了来自 crud 存储库的saveAll。这也适用于内存。 GC 现在在内存可视化中生成预期的链锯模式。但是我仍然不知道为什么我之前使用注释中的连接表创建的多对多关系会产生有关内存管理的问题。有人可以解释为什么这可以解决我的问题是 ManyToMany 创建循环依赖项吗?但据我所知,GC 也会发现循环依赖。

    【讨论】:

      【解决方案3】:

      ManyToMany 中的 EAGER 关系会产生许多对象。关于 LAZY 属性,请确保获取它们,因为如果您不这样做,则通过完整的对象将其转换为 JSON 或 POJO 将为每个尚未使用 fetch 初始化的对象抛出一个查询,这很危险。如果您不需要所有这些,您可以使用 @JsonIgnore 注释。

      【讨论】:

      • 但是我怎样才能更新它哪个合并?不确定您是否收到问题?
      • 我完全确定这个问题,例如,如果在 QueryEntity 类中,您已经将 newsPost 类的关系定义为 EAGER,那么您将创建对象的循环初始化,这将很快就会溢出内存。关于对象更新,在 EAGER 和 LAZY 中都可以以相同的方式完成,只要您之前进行了查找(您这样做)并且您没有退出事务。
      • 在将实体映射到平面对象时,您是否尝试使用 @JsonIgnore 注释来中断双向初始化?
      猜你喜欢
      • 1970-01-01
      • 2016-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-05
      • 2020-09-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多