save()上的Spring Boot JPARepository性能

时间:2018-08-21 11:06:22

标签: java spring hibernate spring-boot spring-data-jpa

我遇到一个问题,即插入数据时我的Spring Boot应用程序性能非常慢。

我正在从一个数据库中提取大量数据并将其插入到另一个数据库中。

以下是我的实体。

@Entity
@Table(name = "element")
public class VXMLElementHistorical {

@Id
@Column(name = "elementid")   
private long elementid;

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

Getter/Setter methods...    

我已经配置了一个JPA存储库

public interface ElementRepository extends JpaRepository<Element, Long> {

}

并使用我的对象调用save()方法

@Transactional 
public void processData(List<sElement> hostElements) 
throws DataAccessException { 

List<Element> elements = new ArrayList<Element>();    

for (int i = 0; i < hostElements.size(); i++) {
        Element element = new Element();
        element.setElementid(hostElements.get(i).getElementid());
        element.setElementname(hostElements.get(i).getElementname());
        elements.add(element);
    }

   try{
   elementRepository.save(elements);{
   //catch etc...

}

发生的事情是,对于每个项目,执行插入操作都需要6到12秒。我已经打开了休眠跟踪记录和统计信息,当我调用保存功能时,发生的事情是休眠执行了两个查询,一个选择和一个插入。选择查询占用了总时间的99%。

我直接在数据库上运行了选择查询,结果以纳秒为单位返回。这使我相信这不是索引问题,但是我不是DBA。

我已经在我的开发环境中创建了一个负载测试,并且具有相似的负载大小,总的处理时间远没有在产品环境中那么长。

有什么建议吗?

3 个答案:

答案 0 :(得分:0)

为@M。 Deinum在评论中说,您可以通过在一定数量的插入之后调用flush()clear()来进行改进,如下所示。

int i = 0;
for(Element element: elements) {
    dao.save(element);
    if(++i % 20 == 0) {
        dao.flushAndClear();
    }

}

答案 1 :(得分:0)

保存单个元素,而不是创建元素列表并保存它们。然后每执行一次flushclear,以防止脏检查成为瓶颈。

@PersistenceContext
private EntityManager entityManager;

@Transactional 
public void processData(List<sElement> hostElements) 
throws DataAccessException {     

for (int i = 0; i < hostElements.size(); i++) {
        Element element = new Element();
        element.setElementid(hostElements.get(i).getElementid());
        element.setElementname(hostElements.get(i).getElementname());
        elementRepository.save(element)
        if ( (i % 50) == 0) {
            entityManager.flush();
            entityManager.clear();
        }
}
entityManager.flush(); // flush the last records.

您要刷新+清除每个x元素(此处为50,但是您可能希望找到自己的最佳数字。

现在,当您使用Spring Boot时,您可能还想添加一些其他属性。就像配置批处理大小一样。

spring.jpa.properties.hibernate.jdbc.batch_size=50 

如果您的JDBC驱动程序支持,它将把50个单插入语句转换为1个大批量插入。即50次插入到1次插入。

另请参见https://vladmihalcea.com/how-to-batch-insert-and-update-statements-with-hibernate/

答案 2 :(得分:0)

由于加载实体似乎是瓶颈,而您实际上只是想进行插入,即您知道实体在数据库中不存在,因此您可能不应该使用Spring Data的标准save方法JPA。

原因是它执行merge,触发Hibernate加载数据库中可能已经存在的实体。

相反,将custom method添加到您的存储库中,该存储库在实体管理器上执行persist。由于您是预先设置的Id,因此请确保具有版本属性,以便Hibernate可以确定这确实是一个新实体。

这应该会使选择消失。

其他答案中给出的其他建议值得考虑作为第二步:

  • 启用批处理。
  • 具有中间冲洗和会话清除功能的实验。
  • 一次保存一个实例而不将其收集到一个集合中,因为对mergepersist的调用实际上不会触发写入数据库,但是只有刷新会触发(这是一个简化,但在这种情况下应如此)