JPA:EntityManager保存数据的时间太长。
JPA:EntityManager保存数据的时间太长。
我有一个包含10万条记录的数据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
每次迭代将更新的五个表如下:
- Master
- MasterDescription
- Attributes
- AttributeValues
- AssociationTable
上述表之间的关系如下:
Master M-M AttributeValues
Master M-1 MatserDescription
Master M-M Attributes
Attributes 1-M AttributeValues
以下是我使用批处理技术将CSV数据保存到5个表中的代码:
服务类
@Service public class EanService{ @AutoWired public EanRepository eanrepository; // 将数据从CSV保存到数据库的方法 @Transactional public void saveEANMasterData1(BufferedReader br, String userName, Listattributes, 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 eans = new ArrayList (); // 迭代CSV中的每条记录并将数据保存到数据库中 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())){ // 持久化Ean;我认为这个方法有些问题 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万条记录。我们如何缩短时间并解决问题?
问题出现的原因:
在这个web应用程序中,用户需要上传数据,然后根据上传的数据推断出模式,用户选择需要保存的列,然后将这些列保存到数据库中。这个过程可能会导致EntityManager保存数据的时间过长。
解决方法:
可以将文件先保存到磁盘上,然后调用数据库特定的工具来处理这个文件,这些工具更适合处理这种类型的数据。这样做还更加用户友好,因为用户不需要等待数据的持久化过程。
代码示例:
// 保存文件到磁盘上 String filePath = "path/to/save/file.csv"; File file = new File(filePath); // 将上传的文件保存到磁盘上 // ... // 调用数据库特定的工具来处理文件 String importCommand = "database-specific-import-command " + filePath; Runtime.getRuntime().exec(importCommand); // 删除保存在磁盘上的文件 file.delete();
通过将数据保存到磁盘上,并使用数据库特定的工具来处理文件,可以更快地将数据导入到数据库中,并提高用户体验。
JPA : EntityManager保存数据时间过长的原因和解决方法
在批处理应用程序中,我遇到了同样的问题,并且我们采用了两种技术来大大加快导入数据的过程:
1) 多线程 - 利用多个线程处理文件数据并进行保存。
我们的做法是首先从文件中读取所有数据,并将其打包成一组POJO对象。
然后根据可能创建的线程数量,将集合平均分割,并将一定范围的数据提供给线程。
然后每个集合都会并行处理。
我不会详细介绍这个过程,因为它超出了这个问题的范围。我可以给出的一个提示是,尝试利用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) Flush计数 - 你将计数设置为在刷新到数据库之前的50次...现在在启用批量插入的情况下,可以将其提高到几百次...尝试使用这个数字进行实验,找到最佳值。