一. 异常报告

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 exists
		if (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 sheets
            String sheetName = XSSFRelation.WORKSHEET.getFileName(sheetNumber);
            for(POIXMLDocumentPart relation : getRelations()) {
                if(relation.getPackagePart() != null && 
                        sheetName.equals(relation.getPackagePart().getPartName().getName())) {
                    // name is taken => try next one
                    sheetNumber++;
                    continue outerloop;
                }
            }

            // no duplicate found => use this one
            break;
        }

        RelationPart rp = createRelationship(XSSFRelation.WORKSHEET, XSSFFactory.getInstance(), sheetNumber, false);
       ......
    }

  可以看出,当前方法线程不安全,多线程下同一个Workbook对象可能产生相同的sheetNumber,从而导致文中最上面产生的问题。

三. 解决方法

   在创建sheet时,加同步锁

SXSSFSheet sheet;
synchronized (ExportOrder.class){
    sheet=wb.createSheet(goodsOrderRequestDto.getSheetName());
}
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐