应用场景举例:需要查数据库获取明细数据若干行,并且汇总得到一行汇总数据。现在需要将汇总数据写入文件第一行,然后将若干行(假设10w行)明细数据从第二行一直写到100001行。
实现方式:
方法一:
(1)查询数据库:在数据库sql中做统计得到汇总行数据,写入文件中。
(2)查询数据库:在数据库sql中直接的分页查询(不统计)得到若干行明细数据,并不停的将分页的明细数据追加写入到文件中。
可以很easy的完成,但是问题来了:当数据量太大的时候,数据库做sum运算压力很大。不妨将(1)中的sql运算改为直接在数据库sql中直接的分页查询(不统计)得到若干行明细数据在内存中循环计算得到汇总数据,但是因为全部的明细数据太大了不能缓存在内存中所以第二步的时候仍需要做分页查询明细数据这件事,效率很低~~~且看下面的其它方案。
方法二:
(1)查询数据库:在数据库sql中直接的分页查询(不统计)得到若干行明细数据,同时累加计算汇总数据,并不停的将分页的明细数据追加写入到目标文件中。
(2)当往目标文件中写完全部的明细数据时候正好也计算好了汇总行数据。
(3)新建一个临时文件,将目标文件中的全部的明细数据用流读出并写入到tmp文件中。
(4)将汇总行数据写入到目标文件(会覆盖原文件内容),将tmp文件中的内容追加回写到目标文件。
(5)将tmp文件删除掉。
方法三:(比较机智的巧妙地方案~~~)
(1)(2)同方法二
(3)新建一个临时文件,将汇总行数据写入该临时文件。
(4)将目标文件中的全部的明细数据用流读出并写入到tmp文件中。【此时tmp文件中就是全部的所要的数据】
(4)将tmp文件重命名为原始文件,删掉原始文件。
接下来呈上热腾腾的代码【仅针对方法二,方法三更加简单各位自由发挥吧~~~】
注:业务场景为:查询数据库获取交易对账明细数据和计算出汇总数据,将汇总数据写入文件第一行,明细数据顺延的写入文件中,生成对账文件。
/**
* 生成对账文件:交易明细
* @throws ParseException
*/
public TxReconFileHeadLineModel writeDetailInfo(FileGenerateUtil fileUtil, String merchantId, String accountingDate, String shotMerchantId) throws IOException, ParseException {
TxReconFileHeadLineModel headLine = new TxReconFileHeadLineModel();
headLine.setMerchantId(shotMerchantId);
headLine.setAccountingDate(accountingDate);
headLine.setCurrency("CNY");
//初始化,用于累加计算
BigDecimal paymentTransactionAmount = BigDecimal.ZERO;
int paymentTransactionCount = 0;
BigDecimal refundTransactionAmount = BigDecimal.ZERO;
int refundTransactionCount = 0;
BigDecimal paymentTransactionFee = BigDecimal.ZERO;
BigDecimal refundTransactionFee = BigDecimal.ZERO;
SettleOrderDataCenter dateCenterSearch = new SettleOrderDataCenter();
dateCenterSearch.setAccountingDate(new SimpleDateFormat("yyyyMMdd").parse(accountingDate));
dateCenterSearch.setMerchantId(merchantId);
int pageSize = 1000, currPage = 0;
while(true) {
Pageable pageable = new PageRequest(currPage++, pageSize, Direction.ASC, "id");
List dataCenterList = this.settleOrderDataCenterRepo.selectPage(dateCenterSearch, pageable);
if (null == dataCenterList || dataCenterList.size() == 0) {
break;
}
for (int i = 0; i < dataCenterList.size(); i++) {
SettleOrderDataCenter datacenter = dataCenterList.get(i);
int paymentRefundFlag = "1".equals(datacenter.getTransType().toString()) ? 1 : -1;//1-交易,2退款',
String detailStr = String.format("\r\n%s,%s,%s,%s,%s,%s,%s,%s,%s",
accountingDate,
shotMerchantId,
datacenter.getTransType(),
StringUtils.trimToEmpty(datacenter.getMerchantOrderNo()),
DateUtil.dateToString(datacenter.getOrderTime(), DateUtil.YYYYMMDDHHMMSS),
datacenter.getOrderId(),
datacenter.getCurrency(),
datacenter.getOrderAmount().multiply(new BigDecimal(paymentRefundFlag)).divide(new BigDecimal(100)).setScale(2),
datacenter.getFee().multiply(new BigDecimal(paymentRefundFlag * -1)).divide(new BigDecimal(100)).setScale(2));
fileUtil.write(detailStr);
//累加计算
if (paymentRefundFlag == 1) {
paymentTransactionCount++;
paymentTransactionAmount = paymentTransactionAmount.add(datacenter.getOrderAmount());
paymentTransactionFee = paymentTransactionFee.add(datacenter.getFee());
} else {
refundTransactionCount++;
refundTransactionAmount = refundTransactionAmount.add(datacenter.getOrderAmount());
refundTransactionFee = refundTransactionFee.add(datacenter.getFee());
}
}
fileUtil.flush();
}
headLine.setPaymentTransactionCount(String.valueOf(paymentTransactionCount));
headLine.setPaymentTransactionAmount(paymentTransactionAmount.divide(new BigDecimal(100)).setScale(2).toString());
headLine.setRefundTransactionCount(String.valueOf(refundTransactionCount));
headLine.setRefundTransactionAmount(refundTransactionAmount.divide(new BigDecimal(100)).setScale(2).toString());
headLine.setTransactionFee(paymentTransactionFee.subtract(refundTransactionFee).divide(new BigDecimal(100)).setScale(2).toString());
return headLine;
}
该方法主要是实现方法二中的(1)(2)。
/*
* 插入到指定位置
* @param fileName 文件路径+文件名称
* @param pos 插入的位置 【首行的话是0】
* @param insertContent 待插入的数据
*/
public void insert(String filename,int pos,String insertContent) throws IOException{
File tmp = File.createTempFile("tmp", null);
RandomAccessFile raf = new RandomAccessFile(filename, "rw");
// BufferedInputStream buffIn = new BufferedInputStream(new FileInputStream(new File(filename)));
BufferedOutputStream tmpOut = new BufferedOutputStream(new FileOutputStream(tmp));
BufferedInputStream tmpIn = new BufferedInputStream(new FileInputStream(tmp));
System.out.println(raf.length());
raf.seek(pos);
byte[] buf = new byte[64];
int hasRead;
//System.out.println(raf.read(buf));
while((hasRead = raf.read(buf))>0){
//把原有内容读入临时文件
tmpOut.write(buf, 0, hasRead);
}
tmpOut.flush();
raf.seek(pos);
raf.write(insertContent.getBytes());
//追加临时文件的内容
while((hasRead = tmpIn.read(buf))>0){
raf.write(buf,0,hasRead);
}
//关闭工作流
tmpIn.close();
tmpOut.close();
raf.close();
//在JVM进程退出的时候删除文件,通常用在临时文件的删除.
tmp.deleteOnExit();
}
该方法是实现方法二中的(3)(4)(5)
public static void main(String[] args) throws IOException {
//路径为 -> D:/apps/reconfile/beifu/{shotMerchantId}/trans/文件名.csv
String pathname = "D:/apps/reconfile/beifu/shotMerchantId/trans/test.csv";
FileGenerateUtil fileUtil = new FileGenerateUtil();
//记账日期 商户号 币种 消费总笔数 消费总金额 退款总笔数 退款总金额 手续费总金额
String summaryTitle = String.format("%s,%s,%s,%s,%s,%s,%s,%s\n",
"accountingDate",
"merchantId",
"currency",
"paymentTransactionCount",
"paymentTransactionAmount",
"refundTransactionCount",
"refundTransactionAmount",
"transactionFee");
//将汇总行数据插入到文本的第一行
fileUtil.insert(pathname, 0, summaryTitle);
}
该方法为测试类~~~~
这样就可以成功的搞定啦~代码段中第一个涉及具体业务仅供参考,第二个可以直接拿走复用~~~
如有疑问,欢迎一起交流分享