一. 异常报告
java.util.concurrent.ExecutionException: org.apache.poi.openxml4j.exceptions.PartAlreadyExistsException:
A part with the name '/xl/worksheets/sheet1.xml' already exists : Packages shall not
contain equivalent part names and package implementers shall neither create nor recognize
packages with equivalent part names. [M1.12]at java.util.concurrent.FutureTask.report(FutureTask.java:122)at java.util.concurrent.FutureTask.get(FutureTask.java:192)at com.xlcloud.business.vip.util.parallel.ParallelQueryExecutor.execute(ParallelQueryExecutor.java:62)at com.xlcloud.business.vip.service.orders.NewExportGoodsOldOrderService.newExportGoodsOrderList(NewExportGoodsOldOrderService.java:54)at com.xlcloud.business.vip.service.orders.NewExportGoodsOldOrderService$$FastClassBySpringCGLIB$$cf8ecc61.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115)at org.springframework.aop.interceptor.AsyncExecutionInterceptor$$Lambda$714/372457144.call(Unknown Source)at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)at java.util.concurrent.FutureTask.run(FutureTask.java)at java.lang.Thread.run(Thread.java:745)
Caused by: org.apache.poi.openxml4j.exceptions.PartAlreadyExistsException: A part with the name '/xl/worksheets/sheet1.xml' already exists : Packages shall not contain equivalent part names and package implementers shall neither create nor recognize packages with equivalent part names. [M1.12]at org.apache.poi.openxml4j.opc.OPCPackage.createPart(OPCPackage.java:889)at org.apache.poi.openxml4j.opc.OPCPackage.createPart(OPCPackage.java:853)at org.apache.poi.POIXMLDocumentPart.createRelationship(POIXMLDocumentPart.java:558)at org.apache.poi.xssf.usermodel.XSSFWorkbook.createSheet(XSSFWorkbook.java:858)at org.apache.poi.xssf.streaming.SXSSFWorkbook.createSheet(SXSSFWorkbook.java:677)at com.xlcloud.business.vip.service.orders.PayOrderNewOldTask.orderAreaExcel(PayOrderNewOldTask.java:52)at com.xlcloud.business.vip.service.orders.PayOrderNewOldTask.results(PayOrderNewOldTask.java:41)at com.xlcloud.business.vip.util.parallel.ParallelTask.call(ParallelTask.java:20)at com.xlcloud.business.vip.util.parallel.ParallelTask.call(ParallelTask.java:10)at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)at java.util.concurrent.FutureTask.run(FutureTask.java)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)... 1 common frames omitted
二. 产生背景和解决方案
?? 问题背景: 使用多线程绘制excel表格,每个线程创建一个sheet,测试服偶现上面的异常信息,且频率较高。看源码也没发现什么问题,传入的sheet名称确定是完全不同的。一时间没有定位问题。
?? 在后来,觉得是多线程绘制表格产生的问题。再次看源码,发现问题:
1)报错位置
PackagePart createPart(PackagePartName partName, String contentType,boolean loadRelationships) {
throwExceptionIfReadOnly();if (partName == null) {
throw new IllegalArgumentException("partName");}if (contentType == null || contentType.equals("")) {
throw new IllegalArgumentException("contentType");}// Check if the specified part name already existsif (partList.containsKey(partName)&& !partList.get(partName).isDeleted()) {
throw new PartAlreadyExistsException("A part with the name '" + partName.getName() + "'" +" already exists : Packages shall not contain equivalent part names and package" +" implementers shall neither create nor recognize packages with equivalent part names. [M1.12]");}.......
}
2)向上查找
?? 由报错位置可知partName重复了,继续往上找
protected final RelationPart createRelationship(POIXMLRelation descriptor, POIXMLFactory factory, int idx, boolean noRelation){
try {
PackagePartName ppName = PackagingURIHelper.createPartName(descriptor.getFileName(idx));PackageRelationship rel = null;PackagePart part = packagePart.getPackage().createPart(ppName, descriptor.getContentType());......}}
?? 由上可知 partName 是由 descriptor 中根据idx获取的,idx可能在多线程中传入的是相同的
public XSSFSheet createSheet(String sheetname) {
......int sheetNumber = 1;outerloop:while(true) {
for(XSSFSheet sh : sheets) {
sheetNumber = (int)Math.max(sh.sheet.getSheetId() + 1, sheetNumber);}// Bug 57165: We also need to check that the resulting file name is not already taken// this can happen when moving/cloning sheetsString sheetName = XSSFRelation.WORKSHEET.getFileName(sheetNumber);for(POIXMLDocumentPart relation : getRelations()) {
if(relation.getPackagePart() != null && sheetName.equals(relation.getPackagePart().getPartName().getName())) {
// name is taken => try next onesheetNumber++;continue outerloop;}}// no duplicate found => use this onebreak;}RelationPart rp = createRelationship(XSSFRelation.WORKSHEET, XSSFFactory.getInstance(), sheetNumber, false);......}
??可以看出,当前方法线程不安全,多线程下同一个Workbook对象可能产生相同的sheetNumber,从而导致文中最上面产生的问题。
三. 解决方法
?? 在创建sheet时,加同步锁
SXSSFSheet sheet;
synchronized (ExportOrder.class){
sheet=wb.createSheet(goodsOrderRequestDto.getSheetName());
}