JPA:EntityManager需要很长时间才能保存数据

时间:2017-02-19 08:33:43

标签: java spring hibernate jpa orm

我有一个csv数据文件,共有10万条记录。我正在迭代记录并尝试为每条记录更新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. MasterDescription
  2. 属性
  3. AttributeValues
  4. AssociationTable
  5. 上述表格之间的关系如下:

    掌握M-M属性值

    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小时)来完成所有10万条记录的保存。我们怎样才能减少时间和缺少的东西?

3 个答案:

答案 0 :(得分: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 ..尝试试验这个数字来找到最佳点。

答案 1 :(得分:0)

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

所以我只是根据自己的经验盲目拍摄:

  • 如果可能,使用persist()而不是merge(),merge()会产生一个select并进行一些值复制

  • 加载大量数据时 - 请勿使用交易。我看到你只在每50条记录上刷新一次,但是交易开销可能仍然非常昂贵

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

答案 2 :(得分:0)

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

例如,对于MySQL,您有these tools以及load data infile;对于PostgreSQL,this syntax可以使用from command line;对于Oracle,sqlldr。那些更适合这类事情。