我的应用程序可以包含许多不同的项目,这些项目包含通过OneToOne
,OneToMany
或ManyToMany
关系链接的实体。
业务需求是能够克隆整个项目并创建一个新项目。有时这些实体具有基于项目名称的唯一代码,因此我需要计算新代码。
有关信息,我将 Spring 5.1.12.RELEASE 与 Hibernate 5.3.14.Final 结合使用,并在 Java 11 上运行 Postgres 11.8 。
我的第一个天真解决方案是遍历每个对象,然后遍历其每个子对象以重新创建所有外键。但是,它将对每个父对象执行选择查询,这对性能非常不利。
我当前的解决方案是,对于每个实体,(使用联接)获取所有当前项目实体,然后对其进行克隆。但是,为了跟踪外键,我创建了HashMap
链接将旧的父实体链接到新的父实体,因此我可以轻松地在子实体中进行设置。
例如,假设我有实体A和B是实体C的父母。
克隆实体A和B时,我会做:
oldAtoNewAMap.put((A)Hibernate.unproxy(oldA),newA);
oldBtoNewBMap.put((B)Hibernate.unproxy(oldB),newB);
然后克隆C时,我会做类似的事情:
C newC = new C(oldC); // copy constructor copying everything but the ID
A newA = oldAToNewAMap.get((A) Hibernate.unproxy(oldC.geta()));
B newB = oldBToNewBMap.get((B) Hibernate.unproxy(oldC.getB()));
newA.addC(newC); // updates the collection in the parent and sets the parent in the child
newB.addC(newC);
当不再使用地图时,我将其清除。
我已经启用了Hibernate批处理,并且每25次迭代(其中25次是我的批处理大小)调用entityManager.flush()
和entityManager.clear()
。我的ID是使用GenerationType.SEQUENCE
生成的。
此外,我使用Spring的Streamed查询来获取当前项目的实体:
@QueryHints(value = {
@QueryHint(name = HINT_CACHEABLE,value = "false"),@QueryHint(name = HINT_FETCH_SIZE,value = "25"),@QueryHint(name = READ_ONLY,value = "true")
})
@Query("select c from C c inner join c.parent1 p1 inner join p1.parent2 p2 where p2.projectId = ?1")
Stream<C> findAllByProjectId(Long projectId);
我的问题是要复制很多实体(在该示例中,将有〜400,000个C实体,〜6000 A实体和 60个B实体)。然后这些C实体也有子...
因此,起初需要1-2秒来处理1,000个C实体。但是,时间量一直保持相当快的增长,在处理完60,000个实体之后,要花10秒的时间才能处理1000个C实体。
很明显,这将是在晚上运行的一个批处理,以避免表锁定,因此我不需要超级快,但我仍然需要第二天早上准备好我的新项目。我有什么办法可以在不运行太多选择的情况下管理内存问题?
也许最好的方法是使用其他工具来完成工作?
编辑:重新启用Hibernate的show-sql
配置后,我意识到我的代码每次循环时仍对B表上的每个C实体执行一个SELECT
在流上。我尝试在left join fetch
的B表中添加一个@Query
,但没有任何改变。
编辑2:显然,Hibernate也不会批处理我的语句吗?它显示了25条插入语句,即使spring.jpa.properties.hibernate.jdbc.batch_size
设置为25,我也使用Spring的saveAll()函数。我不知道这是怎么回事...