【发布时间】: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 响应映射到我的实体类。
之后我尝试了两种不同的方法:
- 我将查询 ID 作为设置添加到 NewsPost 并使用
EntityManager的合并选项将其写回 DB。这工作得很好,直到我说到点子上,我再次得到一个 NewsPost 以进行另一个查询,然后新的查询被旧的查询覆盖。为了解决这个问题,它尝试了 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。我尝试添加额外的请求信息。
-
您的模型有点难以理解:您在开始时提到了您的三个实体。您的三个实体的实现虽然链接到其他一些实体。忽略拼写错误,我发现
NewsPost、NewsSite、QueryEntity、SidSocialgistQueryEntity和SocialgistBoardPostsEntity。能否请您描述一下您的模型或更正它?
标签: java jpa spring-data-jpa