【问题标题】:JPA : EntityManager is taking too long to save the dataJPA:EntityManager 保存数据的时间太长
【发布时间】:2017-07-08 14:05:54
【问题描述】:

我有一个包含 100 000 条记录的 csv 数据文件。我正在遍历记录并尝试为每条记录更新 5 个表。这是示例数据:

EAN Code,Site,Genric Material,Material,Sap Ean Code,Style,Color,Size,MRP,Gender,EAN Code,Season,Collection,BRAND,Color revision,Category (L5),Category (L6)
123456789,6001,000000000061000102,000000061000102001,61000102001,03/BE100,SC/TG,L/112 cm,850.00,MENS,123456789,AW12,Colors,XXXXXX,RD/TG,Tee Shirt,Graphic

每次迭代都会更新的五个表如下:

  1. 大师
  2. 主描述
  3. 属性
  4. 属性值
  5. 关联表

上述表之间的关系如下:

掌握 M-M AttributeValues

Master M-1 MatserDescription

掌握 M-M 属性

属性 1-M AttributeValues

这是我必须使用批处理技术在单个会话中将 CSV 数据保存到 5 个表中的代码:

服务类

@Service
public class EanService{

@AutoWired
public EanRepository eanrepository;

// Method that saves data from CSV to DataBase
@Transactional
public void saveEANMasterData1(BufferedReader br, String userName,
        List<EanAttributes> attributes, String eanMasterName,String description) {
    int i =1;

    EanMasterDiscription eanDes = new EanMasterDiscription();
    User user = userRepository.findUserByUsername(userName);
    EanMasterDiscription deciption = null;
    eanDes.setDescription(description);
    eanDes.setMasterName(eanMasterName);
    eanDes.setDate(new Timestamp(Calendar.getInstance()
            .getTimeInMillis()));
    String line;
    try {
        List<Ean> eans = new ArrayList<Ean>();
        // iterating over each record in the CSV and saving the data into DB            
        while (((line = br.readLine()) != null)) {
             String[] cols = line.split(",");
             // Style Keeping Unit
             Ean ean = new Ean();
             for(EanAttributes attr : attributes){
                 EanAttributeValues eanAttributeValues = new EanAttributeValues();
                 if(attr.getAttrInferredType().equalsIgnoreCase("EAN")){
                         ean.setEAN(cols[attr.getAttributeOrder()]);
                 }else if(attr.getAttrInferredType().equalsIgnoreCase("Season")){
                     ean.setSeason(cols[attr.getAttributeOrder()]);
                 }else {
                     if(attr.getAttrInferredType().equalsIgnoreCase("Attribute")){
                         EanAttributes eanAttr = eanrepository.loadAttrsListByAttName(attr.getAttributeName());
                         if(eanAttr == null){
                             eanAttributeValues.setAttributeValue(cols[attr.getAttributeOrder()]);
                             eanAttributeValues.setEanAttributes(attr);
                             ean.getEanAttributeValues().add(eanAttributeValues);
                             ean.getEanAttributes().add(attr);
                             attr.getEan().add(ean);
                         }else{
                             ean.getEanAttributes().add(eanAttr);
                             eanAttr.getEan().add(ean);
                             if(eanrepository.isAttributeValueAvailable(cols[attr.getAttributeOrder()])){
                                 eanAttributeValues.setAttributeValue(cols[attr.getAttributeOrder()]);
                                 eanAttributeValues.setEanAttributes(eanAttr);
                                 ean.getEanAttributeValues().add(eanAttributeValues);
                             }else{
                                 EanAttributeValues values = eanrepository.loadDataByAttrValue(cols[attr.getAttributeOrder()]);
                                 ean.getEanAttributeValues().add(values);
                                 values.getEan().add(ean);
                             }
                         }
                         eanAttributeValues.getEan().add(ean);
                     }
                 }
             }
             if(!eanrepository.isEanMasterNameAvailable(eanMasterName)){
                EanMasterDiscription eanMasterDes = eanrepository.loadDataByMasterName(eanMasterName);
                 ean.setEanMasterDesciption(eanMasterDes);
             }else{
                 ean.setEanMasterDesciption(eanDes);
             }
             ean.setUser(user);
             if(eanrepository.isEanWithSeasonAvailable(ean.getEAN(),ean.getSeason())){
                     // Persisting Ean; I think there is some problem with this method
                     eanrepository.saveEanData(ean,i);
             }else{
                 System.out.println("************ EAN ALREADY EXIST ******************** ");
             }

             i++;
        }
    } catch (NumberFormatException | IOException e) {
        e.printStackTrace();
    }       
    }
}

存储库类

@Repository
public class EanRepository{

@PersistanceContext
EntityManager em;

public void saveEanData(Ean ean , int recordNum){
    em.merge(ean);
    if(recordNum % 50 == 0){
        em.flush();
        em.clear();
        // em.getEntityManagerFactory().getCache().evictAll();
    }
}

}

但这需要太多时间(将近 10 小时)才能完成保存所有 100 000 条记录。我们怎样才能减少时间和我错过了什么?

【问题讨论】:

标签: java spring hibernate jpa orm


【解决方案1】:

我在批处理应用程序中遇到了同样的问题,我们采用了两种技术,大大加快了导入数据的过程:

1) 多线程 - 您必须利用多个线程处理文件数据并进行保存。

我们这样做的方式是首先从文件中读取所有数据并将其打包到一组 POJO 对象中。

然后根据我们可以创建的可能线程的数量,我们将平均拆分 Set,并为线程提供一定范围的数据。

然后每个集合将被并行处理。

我不打算详细介绍,因为这超出了这个问题的范围。我可以给出的提示是,您应该尝试利用 java.util.concurrent 及其提供的功能。

2) 批量保存 - 我们所做的第二个改进是利用 hibernate 的批量保存功能(您已添加 Hibernate 标记,因此我假设这是您的底层持久性提供程序):

您可以尝试利用批量插入功能。

您可以定义 hibernate 属性来启用此功能:

<property name="jdbc.batch_size">250</property>

使用此批处理设置,您应该有如下输出:

insert into Table(id , name) values (1, 'na1') , (2, 'na2') ,(3, 'na3')..

而不是

insert into Table(id , name) values (1, 'na1');
insert into Table(id , name) values (2, 'na2');
insert into Table(id , name) values (3, 'na3');

3) 刷新计数 - 在刷新到数据库之前,您已将计数设置为 50 .. 现在启用批量插入也许您可以将其提高到几个 houndread.. 尝试试验这个数字以找到最佳位置。

【讨论】:

    【解决方案2】:

    加载数据需要时间的原因可能有很多(因此只能从它所在的代码中),并且 - 您应该优化较小的数据块。

    所以我只是根据我的经验盲目射击:

    • 如果可能,使用persist() 代替merge(),merge() 会产生更多选择并进行一些值复制

    • 在加载大量数据时 - 不要使用事务。我看到你只在每 50 条记录上刷新一次,但无论如何事务开销可能仍然非常昂贵

    • 如上一篇文章中所写,设置批量插入属性(取决于使用的 JPA)

    【讨论】:

      【解决方案3】:

      由于您有一个包含大量条目的 CSV 文件,我会考虑另一种选择:特定于数据库的单独工具。

      例如,对于 MySQL,您将拥有 these toolsload data infile;对于 PostgreSQL,this syntax 可以使用 from command line;对于甲骨文,sqlldr。这些针对此类事情进行了更多优化。

      【讨论】:

      • 实际上这里的问题是这是一个 Web 应用程序,用户必须上传我们在该架构上推断架构的数据 i 用户将选择他需要的列只有我们需要的那些列将其保存到数据库中。谢谢
      • @RamkumarDM 没有什么能阻止 Web 应用程序将文件保存到磁盘,然后使用文件路径作为参数调用其中一些工具(然后删除文件)。这也会对用户更加友好,因为用户不必等待持久化。
      猜你喜欢
      • 2014-07-27
      • 1970-01-01
      • 2015-10-11
      • 1970-01-01
      • 1970-01-01
      • 2018-12-11
      • 2019-10-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多