使用Spring boot JPA将数百万行从CSV保存到Oracle DB
另一个应用程序定期转储包含超过7-8百万行的CSV。我有一个cron作业,它从CSV加载数据,并将数据保存到oracle数据库中。这是我的代码片段使用Spring boot JPA将数百万行从CSV保存到Oracle DB,spring,spring-boot,jpa,spring-data-jpa,Spring,Spring Boot,Jpa,Spring Data Jpa,另一个应用程序定期转储包含超过7-8百万行的CSV。我有一个cron作业,它从CSV加载数据,并将数据保存到oracle数据库中。这是我的代码片段 String line = ""; int count = 0; LocalDate localDateTime; Instant from = Instant.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(&quo
String line = "";
int count = 0;
LocalDate localDateTime;
Instant from = Instant.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yy");
List<ItemizedBill> itemizedBills = new ArrayList<>();
try {
BufferedReader br=new BufferedReader(new FileReader("/u01/CDR_20210325.csv"));
while((line=br.readLine())!=null) {
if (count >= 1) {
String [] data= line.split("\\|");
ItemizedBill customer = new ItemizedBill();
customer.setEventType(data[0]);
String date = data[1].substring(0,2);
String month = data[1].substring(3,6);
String year = data[1].substring(7,9);
month = WordUtils.capitalizeFully(month);
String modifiedDate = date + "-" + month + "-" + year;
localDateTime = LocalDate.parse(modifiedDate, formatter);
customer.setEventDate(localDateTime.atStartOfDay(ZoneId.systemDefault()).toInstant());
customer.setaPartyNumber(data[2]);
customer.setbPartyNumber(data[3]);
customer.setVolume(Long.valueOf(data[4]));
customer.setMode(data[5]);
if(data[6].contains("0")) { customer.setFnfNum("Other"); }
else{ customer.setFnfNum("FNF Number"); }
itemizedBills.add(customer);
}
count++;
}
itemizedBillRepository.saveAll(itemizedBills);
} catch (IOException e) {
e.printStackTrace();
}
}
字符串行=”;
整数计数=0;
LocalDate localDateTime;
瞬间开始=瞬间。现在();
DateTimeFormatter formatter=模式的DateTimeFormatter.of(“dd-MMM-yy”);
List itemizedBills=new ArrayList();
试一试{
BufferedReader br=新的BufferedReader(新文件读取器(“/u01/CDR_20210325.csv”);
而((line=br.readLine())!=null){
如果(计数>=1){
String[]data=line.split(“\\\\”);
ItemizedBill客户=新的ItemizedBill();
customer.setEventType(数据[0]);
字符串日期=数据[1]。子字符串(0,2);
字符串月份=数据[1]。子字符串(3,6);
字符串年份=数据[1]。子字符串(7,9);
月=大写的字数(月);
字符串modifiedDate=日期+“-”+月份+“-”+年份;
localDateTime=LocalDate.parse(modifiedDate,格式化程序);
customer.setEventDate(localDateTime.atStartOfDay(ZoneId.systemDefault()).toInstant());
customer.setaPartyNumber(数据[2]);
customer.setPartyNumber(数据[3]);
customer.setVolume(Long.valueOf(数据[4]);
customer.setMode(数据[5]);
if(数据[6]。包含(“0”){customer.setfnum(“其他”);}
else{customer.setfnum(“FNF编号”);}
itemizedBills.add(客户);
}
计数++;
}
itemizedBillRepository.saveAll(itemizedBills);
}捕获(IOE异常){
e、 printStackTrace();
}
}
此功能可以正常工作,但需要花费大量时间来处理。如何提高效率并加快此过程?您可以使用spring data batch insert。此链接说明了如何操作:您可以尝试使用Java 8 Streams和spring data JPA对MySQL结果进行流式处理。下面的链接对此进行了详细说明
您应该对代码做几件事
String.split
,虽然方便,但速度相对较慢,因为它每次都会重新编译regexp。最好使用Pattern
和split
方法来减少开销
应用程序中启用批处理。我们将使用50的批量大小(您需要试验适合您的案例的合适批量大小)
spring.jpa.properties.hibernate.jdbc.batch\u size=50
spring.jpa.properties.hibernate.order\u inserts=true
spring.jpa.properties.hibernate.order\u updates=true
然后直接将实体保存到数据库中,每50项执行一次刷新
和清除
。这将刷新数据库的状态并清除一级缓存(这将防止过多的脏检查)
有了以上这些,您的代码应该是这样的
int count = 0;
Instant from = Instant.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yy");
Pattern splitter = Pattern.compile("\\|");
try {
BufferedReader br=new BufferedReader(new FileReader("/u01/CDR_20210325.csv"));
while((line=br.readLine())!=null) {
if (count >= 1) {
String [] data= splitter.split(Line);
ItemizedBill customer = new ItemizedBill();
customer.setEventType(data[0]);
String date = data[1].substring(0,2);
String month = data[1].substring(3,6);
String year = data[1].substring(7,9);
month = WordUtils.capitalizeFully(month);
String modifiedDate = date + "-" + month + "-" + year;
LocalDate localDate = LocalDate.parse(modifiedDate, formatter);
customer.setEventDate(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
customer.setaPartyNumber(data[2]);
customer.setbPartyNumber(data[3]);
customer.setVolume(Long.valueOf(data[4]));
customer.setMode(data[5]);
if(data[6].contains("0")) {
customer.setFnfNum("Other");
} else {
customer.setFnfNum("FNF Number");
}
itemizedBillRepository.save(customer);
}
count++;
if ( (count % 50) == 0) {
this.entityManager.flush(); // sync with database
this.entityManager.clear(); // clear 1st level cache
}
}
} catch (IOException e) {
e.printStackTrace();
}
您还可以进行2项其他优化:
如果volume
属性是long
而不是long
,则应使用long.parseLong(数据[4])代码>取而代之。它保存Long
创建和取消装箱。如果只有10行,这可能不是问题,但如果有数百万行,那么这些毫秒的总和就会增加
使用ddMMMyy
作为DateTimeFormatter
并删除代码中的子字符串
部分。只需执行LocalDate.parse(date[1].toUpperCase(),格式化)
即可获得相同的结果,而无需增加5个String
对象的额外开销
int count=0;
瞬间开始=瞬间。现在();
DateTimeFormatter formatter=模式的DateTimeFormatter.of(“ddMMMyy”);
patternspilter=Pattern.compile(“\\\\”);
试一试{
BufferedReader br=新的BufferedReader(新文件读取器(“/u01/CDR_20210325.csv”);
而((line=br.readLine())!=null){
如果(计数>=1){
String[]data=splitter.split(行);
ItemizedBill客户=新的ItemizedBill();
customer.setEventType(数据[0]);
LocalDate LocalDate=LocalDate.parse(数据[1].toUpperCase(),格式化程序);
customer.setEventDate(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
customer.setaPartyNumber(数据[2]);
customer.setPartyNumber(数据[3]);
customer.setVolume(Long.parseLong(数据[4]);
customer.setMode(数据[5]);
如果(数据[6]。包含(“0”){
客户。SETFNUM(“其他”);
}否则{
客户。设置FNUM(“FNF编号”);
}
itemizedBillRepository.save(客户);
}
计数++;
如果((计数%50)=0){
this.entityManager.flush();//与数据库同步
this.entityManager.clear();//清除一级缓存
}
}
}捕获(IOE异常){
e、 printStackTrace();
}
如果您想耗尽内存并出现性能问题,这是一种解决方法。不要。不要把东西放在列表中,然后全部保存。而是直接使用save
保存单个项目,并在每个x个项目(可能50或100个)之后执行entitymanager.flush
和entitymanager.clear
。性能将得到提高,有关更多提示,请参阅另一件事String。相对而言,split
速度较慢。相反,使用模式
,编译并重用它。这节省了每次需要创建regexp类时的开销。节省内存,因此gc循环。问题是如何解析文本并将其插入数据库。这不是从数据库中读取大量结果。非常感谢您的建议。这意味着很多。