阿里開源項目EasyExcel


官方網站: https://yuque.com/easyexcel
gitHub地址 https://github.com/alibaba/easyexcel

一、引入easyexcel包

Gradle方式引入

implementation("com.alibaba:easyexcel:2.2.7")

實體模型

@Data
public class User {
    private Integer userId;
    private String name;
    private String phone;
    private String email;
    private Long createTime;
}

二、生成Excel

1. 簡單生成Excel文件到本地磁盤

// 1. 獲取用戶
List<User> users = userDao.findAll();

// 2. 寫文件
String fileName = "F://excel/" + System.currentTimeMillis() + ".xlsx";
EasyExcel.write(fileName, User.class).sheet("用戶表").doWrite(users);

在寫文件的時候必須要保證目錄已經存在,否則會報錯。

2. 修改列名稱

默認情況下,使用類的屬性名作為Excel的列表,當然也可以使用@ExcelProperty 注解來重新指定屬性名稱。

@Data
public class User {
    @ExcelProperty(value = "用戶Id")
    private Integer userId;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "手機")
    private String phone;
    @ExcelProperty(value = "郵箱")
    private String email;
    @ExcelProperty(value = "創建時間")
    private Long createTime;
}

這里我們為每一個屬性指定了一個名稱,重新執行前面的代碼,看看效果如何。

3.合並表頭

我們查看@ExcelProperty源碼value的注釋,在寫的時候,如果指定了多個值,會自動進行合並

 /**
 * The name of the sheet header.
 *
 * <p>
 * write: It automatically merges when you have more than one head
 * <p>
 * read: When you have multiple heads, take the first one
 *
 * @return The name of the sheet header
 */
String[] value() default {""};

我們修改一下user類

@Data
public class User {
    @ExcelProperty(value = "用戶Id")
    private Integer userId;
    @ExcelProperty(value = {"用戶基本信息", "姓名"})
    private String name;
    @ExcelProperty(value = {"用戶基本信息", "手機"})
    private String phone;
    @ExcelProperty(value = {"用戶基本信息", "郵箱"})
    private String email;
    @ExcelProperty(value = "創建時間")
    private Long createTime;
}

重新生成excel查看效果。

4. 指定列的位置和排序

@ExcelProperty注解有兩個屬性index和order,不要使用錯了。如果不指定則按照屬性在類中的排列順序來。

    /**
     * Index of column
     *
     * Read or write it on the index of column,If it's equal to -1, it's sorted by Java class.
     *
     * priority: index &gt; order &gt; default sort
     *
     * @return Index of column
     */
    int index() default -1;

    /**
     * Defines the sort order for an column.
     *
     * priority: index &gt; order &gt; default sort
     *
     * @return Order of column
     */
    int order() default Integer.MAX_VALUE;

通過注釋我們知道index是指定改屬性在Excel中列的下標,下標從0開始

@Data
public class User {
    @ExcelProperty(value = "用戶Id", index = 10)
    private Integer userId;
    @ExcelProperty(value = "姓名", index = 11)
    private String name;
    @ExcelProperty(value = "手機")
    private String phone;
    @ExcelProperty(value = "郵箱")
    private String email;
    @ExcelProperty(value = "創建時間")
    private Long createTime;
}

從結果可以看到,用戶Id和姓名這兩列已經移動到設置的位置,其它列依次前移。

下面使用order來看看效果

@Data
public class User {
    @ExcelProperty(value = "用戶Id")
    private Integer userId;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "手機", order = 11)
    private String phone;
    @ExcelProperty(value = "郵箱", order = 10)
    private String email;
    @ExcelProperty(value = "創建時間")
    private Long createTime;
}

直接貼圖

在源碼中我們知道order的默認值為Integer.MAX_VALUE,通過效果我們可以得出結論:order值越小,越排在前面

根據前面和注釋總結得出結論:

  • 優先級:index > order > 默認配置
  • index相當於絕對位置,下標從0開始
  • order相當於相對位置,值越小的排在越前面

5. 自定義轉換器

在讀寫EXCEL時,有時候需要我們進行數據類型轉換,例如我們這里的創建時間,在實體對象中是Long類型,但是這樣直接導出到Excel中不太直觀。我們需要轉換成yyyy-MM-dd HH:mm:ss 格式,此時我們就可以用到轉換器。
下面我們就來掩飾如何自定義轉換器,只需要實現Converter接口,然后再@ExcelProperty中使用就可以了

public class DateTimeConverter implements Converter<Long> {
    private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public Class supportJavaTypeKey() {
        return Long.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    @Override
    public Long convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        return;
    }

    @Override
    public CellData convertToExcelData(Long value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        if (value == null) {
            return new CellData(CellDataTypeEnum.STRING, null);
        }
        LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneId.systemDefault());
        String dateStr = localDateTime.format(dateTimeFormatter);
        return new CellData(dateStr);
    }
}

在convertToExcelData方法中我們實現如何將Java對象轉換成Excel對象,實現轉換過程。

在實體類中引用

public class User {
    @ExcelProperty(value = "用戶Id")
    private Integer userId;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "手機", order = 11)
    private String phone;
    @ExcelProperty(value = "郵箱", order = 10)
    private String email;
    @ExcelProperty(value = "創建時間", converter = DateTimeConverter.class)
    private Long createTime;
}

執行后貼圖看效果

6. 大量數據如何寫入Excel

先看看另外一種寫入Excel的方式

// 1. 獲取用戶
List<User> users = userDao.findAll();

// 2. 寫文件
String fileName = "F://excel/" + System.currentTimeMillis() + ".xlsx";
ExcelWriter excelWriter = null;
try {
    excelWriter = EasyExcel.write(fileName, User.class).build();
    WriteSheet writeSheet = EasyExcel.writerSheet("用戶表").build();
    excelWriter.write(users, writeSheet);
} finally {
    // 別忘了關閉流
    if (excelWriter != null) {
        excelWriter.finish();
    }
}

上面方式我們可以手工控制流的關閉,這樣我們就可以實現多次寫。

基於上面的方式,我們來實現分頁獲取數據,然后將數據寫入Excel中,避免加載的數據過多,導致內存溢出。

Pageable pageable = PageRequest.of(0, 10);

String fileName = "F://excel/" + System.currentTimeMillis() + ".xlsx";
ExcelWriter excelWriter = null;
try {
    excelWriter = EasyExcel.write(fileName, User.class).build();
    WriteSheet writeSheet = EasyExcel.writerSheet("用戶表").build();
    List<User> users;
    do {
        users = userDao.pageQuery(pageable);
        excelWriter.write(users, writeSheet);
        pageable = pageable.next();
    } while (!CollectionUtils.isEmpty(users));
} finally {
    if (excelWriter != null) {
        excelWriter.finish();
    }
}

在使用excelWriter.write方式時務必保證至少執行一次write,這樣是為了將sheet和表頭寫入excel,否則打開excel時會報錯。write的第一個參數可以為null。

7. 排除或包含指定字段

1) 包含指定字段,這里字段的名稱為類屬性名稱,而不是Excel中列頭顯示名稱
// 1. 獲取用戶
List<User> users = userDao.findAll();

// 2. 寫文件
String fileName = "F://excel/" + System.currentTimeMillis() + ".xlsx";
EasyExcel.write(fileName, User.class)
        .includeColumnFiledNames(Arrays.asList("userId", "name", "phone"))
        .sheet("用戶表").doWrite(users);

2)排除指定字段

下面的效果等價於上面的效果

// 1. 獲取用戶
List<User> users = userDao.findAll();

// 2. 寫文件
String fileName = "F://excel/" + System.currentTimeMillis() + ".xlsx";
EasyExcel.write(fileName, User.class)
        .excludeColumnFiledNames(Arrays.asList("createTime", "email"))
        .sheet("用戶表").doWrite(users);
3)使用注解的方式

在需要排除的字段上添加@ExcelIgnore

@Data
public class User {
    @ExcelProperty(value = "用戶Id")
    private Integer userId;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "手機")
    private String phone;
    @ExcelProperty(value = "郵箱")
    @ExcelIgnore
    private String email;
    @ExcelProperty(value = "創建時間", converter = DateTimeConverter.class)
    @ExcelIgnore
    private Long createTime;
}

8. 設置列寬和行高

@Data
@ContentRowHeight(30)
@HeadRowHeight(40)
@ColumnWidth(20)
public class User {
    @ExcelProperty(value = "用戶Id")
    private Integer userId;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "手機")
    private String phone;
    @ExcelProperty(value = "郵箱")
    private String email;
    @ColumnWidth(25)
    @ExcelProperty(value = "創建時間", converter = DateTimeConverter.class)
    private Long createTime;
}

9. 樣式設置

@Data
@ContentRowHeight(30)
@HeadRowHeight(40)
@ColumnWidth(15)
@HeadStyle(fillBackgroundColor = 14, fillPatternType = FillPatternType.SOLID_FOREGROUND)
@HeadFontStyle(fontName = "黑體", italic = true, color = 10)
public class User {
    @ExcelProperty(value = "用戶Id")
    private Integer userId;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "手機")
    private String phone;
    @ExcelProperty(value = "郵箱")
    private String email;
    @ColumnWidth(25)
    @ExcelProperty(value = "創建時間", converter = DateTimeConverter.class)
    private Long createTime;
}

10. 在Web中生成Excel並下載

1) 方式一:

@GetMapping("/download")
public void downloadExcel(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");

List<User> users = getData();
// 這里URLEncoder.encode可以防止中文亂碼 當然和easyexcel沒有關系

String fileName = URLEncoder.encode("測試", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), User.class).sheet("模板").doWrite(users);

}

2)方式二:
@GetMapping("/download")
public void downloadExcel(HttpServletResponse response) throws IOException {
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding("utf-8");

    // 這里URLEncoder.encode可以防止中文亂碼 當然和easyexcel沒有關系

    String fileName = URLEncoder.encode("測試", "UTF-8").replaceAll("\\+", "%20");
    response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

    Pageable pageable = PageRequest.of(0, 10);
    ExcelWriter excelWriter = null;
    try {
        excelWriter = EasyExcel.write(response.getOutputStream(), User.class).build();
        WriteSheet writeSheet = EasyExcel.writerSheet("用戶表").build();
        List<User> users;
        do {
            users = pageData(pageable);
            excelWriter.write(users, writeSheet);
            pageable = pageable.next();
        } while (!CollectionUtils.isEmpty(users));
    } finally {
        if (excelWriter != null) {
            excelWriter.finish();
        }
    }
}

三、讀取數據

1. 分批讀取並處理數據簡單

通常我們試用這種分批處理的方式,避免內存的消耗。

/**
 * 有個很重要的點 UserListener 不能被spring管理,要每次讀取excel都要new,然后里面用到spring可以構造方法傳進去
 *
 * @author caoyongcheng
 */
public class UserListener extends AnalysisEventListener<User> {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserListener.class);

    /**
     * 每隔10條存儲數據庫,實際使用中可以3000條,然后清理list ,方便內存回收
     */
    private static final int BATCH_COUNT = 10;
    List<User> list = new ArrayList<>();

    private final UserService userService;

    public UserListener(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void invoke(User data, AnalysisContext context) {
        list.add(data);
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存儲完了之后,需要清空列表
            list.clear();
        }
    }

    /**
     * invoke方法執行完后都會調用該方法
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 這里也要保存數據,確保最后遺留的數據也存儲到數據庫
        saveData();
        LOGGER.debug("所有數據解析完成!");
    }

    /**
     * 保存數據
     */
    private void saveData() {
        LOGGER.debug("保存數據條數:{}", list.size());
        userService.save(list);
    }
}

調用方式

@PostMapping("/upload")
public void upload(@RequestParam("file") MultipartFile file) throws IOException {
    if (file.isEmpty()) {
        log.warn("文件為空");
        return;
    }
    EasyExcel.read(file.getInputStream(), User.class, new UserListener(userService)).doReadAll();
}

2. 同步讀取

將excel中的數據直接讀取成list

@PostMapping("/upload")
public void upload(@RequestParam("file") MultipartFile file) throws IOException {
    if (file.isEmpty()) {
        log.warn("文件為空");
        return;
    }
    List<User> users = EasyExcel.read(file.getInputStream(),User.class,null).doReadAllSync();
}

read方法的第三個參數ReadListener直接設置為null,因為不需要處理

3. 指定head頭的行數

如果第二行才是標題,第三行是數據,可以通過headRowNumber(int headRowNumber)來指定

List<UserExcel> users = EasyExcel.read(file.getInputStream(), UserExcel.class, new UserListener())
  .sheet("成員")
  .headRowNumber(2)
  .doReadSync();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM