最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Springboot整合easyexcel實現(xiàn)一個接口任意表的Excel導入導出

 更新時間:2025年02月10日 09:52:51   作者:艾迪的技術(shù)之路  
本文主要介紹了Springboot整合easyexcel實現(xiàn)一個接口任意表的Excel導入導出,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

Java的web開發(fā)需要excel的導入導出工具,所以需要一定的工具類實現(xiàn),如果是使用easypoi、Hutool導入導出excel,會非常的損耗內(nèi)存,因此可以嘗試使用easyexcel解決大數(shù)據(jù)量的數(shù)據(jù)的導入導出,且可以通過Java8的函數(shù)式編程解決該問題。
使用easyexcel,雖然不太會出現(xiàn)OOM的問題,但是如果是大數(shù)據(jù)量的情況下也會有一定量的內(nèi)存溢出的風險,所以我打算從以下幾個方面優(yōu)化這個問題:

  • 使用Java8的函數(shù)式編程實現(xiàn)低代碼量的數(shù)據(jù)導入
  • 使用反射等特性實現(xiàn)單個接口導入任意excel
  • 使用線程池實現(xiàn)大數(shù)據(jù)量的excel導入
  • 通過泛型實現(xiàn)數(shù)據(jù)導出

maven導入

<!--EasyExcel相關依賴-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.0.5</version>
</dependency>

使用泛型實現(xiàn)對象的單個Sheet導入

先實現(xiàn)一個類,用來指代導入的特定的對象

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("stu_info")
@ApiModel("學生信息")
//@ExcelIgnoreUnannotated 沒有注解的字段都不轉(zhuǎn)換
public class StuInfo {

    private static final long serialVersionUID = 1L;
?
    /**
     * 姓名
     */
    // 設置字體,此處代表使用斜體
//    @ContentFontStyle(italic = BooleanEnum.TRUE)
    // 設置列寬度的注解,注解中只有一個參數(shù)value,value的單位是字符長度,最大可以設置255個字符
    @ColumnWidth(10)
    // @ExcelProperty 注解中有三個參數(shù)value,index,converter分別代表表名,列序號,數(shù)據(jù)轉(zhuǎn)換方式
    @ApiModelProperty("姓名")
    @ExcelProperty(value = "姓名",order = 0)
    @ExportHeader(value = "姓名",index = 1)
    private String name;
?
    /**
     * 年齡
     */
//    @ExcelIgnore不將該字段轉(zhuǎn)換成Excel
    @ExcelProperty(value = "年齡",order = 1)
    @ApiModelProperty("年齡")
    @ExportHeader(value = "年齡",index = 2)
    private Integer age;
?
    /**
     * 身高
     */
    //自定義格式-位數(shù)
//    @NumberFormat("#.##%")
    @ExcelProperty(value = "身高",order = 2)
    @ApiModelProperty("身高")
    @ExportHeader(value = "身高",index = 4)
    private Double tall;
?
    /**
     * 自我介紹
     */
    @ExcelProperty(value = "自我介紹",order = 3)
    @ApiModelProperty("自我介紹")
    @ExportHeader(value = "自我介紹",index = 3,ignore = true)
    private String selfIntroduce;
?
    /**
     * 圖片信息
     */
    @ExcelProperty(value = "圖片信息",order = 4)
    @ApiModelProperty("圖片信息")
    @ExportHeader(value = "圖片信息",ignore = true)
    private Blob picture;
?
    /**
     * 性別
     */
    @ExcelProperty(value = "性別",order = 5)
    @ApiModelProperty("性別")
    private Integer gender;
?
    /**
     * 入學時間
     */
    //自定義格式-時間格式
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss:")
    @ExcelProperty(value = "入學時間",order = 6)
    @ApiModelProperty("入學時間")
    private String intake;
?
    /**
     * 出生日期
     */
    @ExcelProperty(value = "出生日期",order = 7)
    @ApiModelProperty("出生日期")
    private String birthday;
}

重寫ReadListener接口

@Slf4j
public class UploadDataListener<T> implements ReadListener<T> {
?
    /**
     * 每隔5條存儲數(shù)據(jù)庫,實際使用中可以100條,然后清理list ,方便內(nèi)存回收
     */
    private static final int BATCH_COUNT = 100;
?
    /**
     * 緩存的數(shù)據(jù)
     */
    private List<T> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
?
    /**
     * Predicate用于過濾數(shù)據(jù)
     */
    private Predicate<T> predicate;
?
    /**
     * 調(diào)用持久層批量保存
     */
    private Consumer<Collection<T>> consumer;
?
    public UploadDataListener(Predicate<T> predicate, Consumer<Collection<T>> consumer) {
        this.predicate = predicate;
        this.consumer = consumer;
    }
?
    public UploadDataListener(Consumer<Collection<T>> consumer) {
        this.consumer = consumer;
    }
?
    /**
     * 如果使用了spring,請使用這個構(gòu)造方法。每次創(chuàng)建Listener的時候需要把spring管理的類傳進來
     *
     * @param demoDAO
     */
?
    /**
     * 這個每一條數(shù)據(jù)解析都會來調(diào)用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(T data, AnalysisContext context) {
?
        if (predicate != null && !predicate.test(data)) {
            return;
        }
        cachedDataList.add(data);
?
        // 達到BATCH_COUNT了,需要去存儲一次數(shù)據(jù)庫,防止數(shù)據(jù)幾萬條數(shù)據(jù)在內(nèi)存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            try {
                // 執(zhí)行具體消費邏輯
                consumer.accept(cachedDataList);
?
            } catch (Exception e) {
?
                log.error("Failed to upload data!data={}", cachedDataList);
                throw new BizException("導入失敗");
            }
            // 存儲完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }
?
    /**
     * 所有數(shù)據(jù)解析完成了 都會來調(diào)用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
?
        // 這里也要保存數(shù)據(jù),確保最后遺留的數(shù)據(jù)也存儲到數(shù)據(jù)庫
        if (CollUtil.isNotEmpty(cachedDataList)) {
?
            try {
                // 執(zhí)行具體消費邏輯
                consumer.accept(cachedDataList);
                log.info("所有數(shù)據(jù)解析完成!");
            } catch (Exception e) {
?
                log.error("Failed to upload data!data={}", cachedDataList);
?
                // 拋出自定義的提示信息
                if (e instanceof BizException) {
                    throw e;
                }
?
                throw new BizException("導入失敗");
            }
        }
    }
}

Controller層的實現(xiàn)

@ApiOperation("只需要一個readListener,解決全部的問題")
    @PostMapping("/update")
    @ResponseBody
    public R<String> aListener4AllExcel(MultipartFile file) throws IOException {
        try {
            EasyExcel.read(file.getInputStream(),
                            StuInfo.class,
                            new UploadDataListener<StuInfo>(
                                    list -> {
                                        // 校驗數(shù)據(jù)
//                                        ValidationUtils.validate(list);
                                        // dao 保存···
                                        //最好是手寫一個,不要使用mybatis-plus的一條條新增的邏輯
                                        service.saveBatch(list);
                                        log.info("從Excel導入數(shù)據(jù)一共 {} 行 ", list.size());
                                    }))
                    .sheet()
                    .doRead();
        } catch (IOException e) {
?
            log.error("導入失敗", e);
            throw new BizException("導入失敗");
        }
        return R.success("SUCCESS");
    }

但是這種方式只能實現(xiàn)已存對象的功能實現(xiàn),如果要新增一種數(shù)據(jù)的導入,那我們需要怎么做呢?
可以通過讀取成Map,根據(jù)順序?qū)氲綌?shù)據(jù)庫中。

通過實現(xiàn)單個Sheet中任意一種數(shù)據(jù)的導入

Controller層的實現(xiàn)

@ApiOperation("只需要一個readListener,解決全部的問題")
    @PostMapping("/listenMapDara")
    @ResponseBody
    public R<String> listenMapDara(@ApiParam(value = "表編碼", required = true)
                                   @NotBlank(message = "表編碼不能為空")
                                   @RequestParam("tableCode") String tableCode,
                                   @ApiParam(value = "上傳的文件", required = true)
                                   @NotNull(message = "上傳文件不能為空") MultipartFile file) throws IOException {
        try {
            //根據(jù)tableCode獲取這張表的字段,可以作為insert與劇中的信息
            EasyExcel.read(file.getInputStream(),
                            new NonClazzOrientedListener(
                                    list -> {
                                        // 校驗數(shù)據(jù)
//                                        ValidationUtils.validate(list);
?
                                        // dao 保存···
                                        log.info("從Excel導入數(shù)據(jù)一共 {} 行 ", list.size());
                                    }))
                    .sheet()
                    .doRead();
        } catch (IOException e) {
            log.error("導入失敗", e);
            throw new BizException("導入失敗");
        }
        return R.success("SUCCESS");
    }

重寫ReadListener接口

@Slf4j
public class NonClazzOrientedListener implements ReadListener<Map<Integer, String>> {
?
    /**
     * 每隔5條存儲數(shù)據(jù)庫,實際使用中可以100條,然后清理list ,方便內(nèi)存回收
     */
    private static final int BATCH_COUNT = 100;
?
    private List<List<Object>> rowsList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
?
    private List<Object> rowList = new ArrayList<>();
    /**
     * Predicate用于過濾數(shù)據(jù)
     */
    private Predicate<Map<Integer, String>> predicate;
?
    /**
     * 調(diào)用持久層批量保存
     */
    private Consumer<List> consumer;
?
    public NonClazzOrientedListener(Predicate<Map<Integer, String>> predicate, Consumer<List> consumer) {
        this.predicate = predicate;
        this.consumer = consumer;
    }
?
    public NonClazzOrientedListener(Consumer<List> consumer) {
        this.consumer = consumer;
    }
?
    /**
     * 添加deviceName標識
     */
    private boolean flag = false;
?
    @Override
    public void invoke(Map<Integer, String> row, AnalysisContext analysisContext) {
        consumer.accept(rowsList);
        rowList.clear();
        row.forEach((k, v) -> {
            log.debug("key is {},value is {}", k, v);
            rowList.add(v == null ? "" : v);
        });
        rowsList.add(rowList);
        if (rowsList.size() > BATCH_COUNT) {
            log.debug("執(zhí)行存儲程序");
            log.info("rowsList is {}", rowsList);
            rowsList.clear();
        }
    }
?
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        consumer.accept(rowsList);
        if (CollUtil.isNotEmpty(rowsList)) {
            try {
                log.debug("執(zhí)行最后的程序");
                log.info("rowsList is {}", rowsList);
            } catch (Exception e) {
?
                log.error("Failed to upload data!data={}", rowsList);
?
                // 拋出自定義的提示信息
                if (e instanceof BizException) {
                    throw e;
                }
?
                throw new BizException("導入失敗");
            } finally {
                rowsList.clear();
            }
        }
    }

這種方式可以通過把表中的字段順序存儲起來,通過配置數(shù)據(jù)和字段的位置實現(xiàn)數(shù)據(jù)的新增,那么如果出現(xiàn)了導出數(shù)據(jù)模板/手寫excel的時候順序和導入的時候順序不一樣怎么辦?
可以通過讀取header進行實現(xiàn),通過表頭讀取到的字段,和數(shù)據(jù)庫中表的字段進行比對,只取其中存在的數(shù)據(jù)進行排序添加

 /**
     * 這里會一行行的返回頭
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        //該方法必然會在讀取數(shù)據(jù)之前進行
        Map<Integer, String> columMap = ConverterUtils.convertToStringMap(headMap, context);
        //通過數(shù)據(jù)交互拿到這個表的表頭
//        Map<String,String> columnList=dao.xxxx();
        Map<String, String> columnList = new HashMap();
        columMap.forEach((key, value) -> {
            if (columnList.containsKey(value)) {
                filterList.add(key);
            }
        });
        //過濾到了只存在表里面的數(shù)據(jù),順序就不用擔心了,可以直接把filterList的數(shù)據(jù)用于排序,可以根據(jù)mybatis做一個動態(tài)sql進行應用
?
        log.info("解析到一條頭數(shù)據(jù):{}", JSON.toJSONString(columMap));
        // 如果想轉(zhuǎn)成成 Map<Integer,String>
        // 方案1: 不要implements ReadListener 而是 extends AnalysisEventListener
        // 方案2: 調(diào)用 ConverterUtils.convertToStringMap(headMap, context) 自動會轉(zhuǎn)換
    }

那么這些問題都解決了,如果出現(xiàn)大數(shù)據(jù)量的情況,如果要極大的使用到cpu,該怎么做呢?
可以嘗試使用線程池進行實現(xiàn)

使用線程池進行多線程導入大量數(shù)據(jù)

Java中線程池的開發(fā)與使用與原理我可以單獨寫一篇文章進行講解,但是在這邊為了進行好的開發(fā)我先給出一套固定一點的方法。
由于ReadListener不能被注冊到IOC容器里面,所以需要在外面開啟
詳情可見Spring Boot通過EasyExcel異步多線程實現(xiàn)大數(shù)據(jù)量Excel導入,百萬數(shù)據(jù)30秒

通過泛型實現(xiàn)對象類型的導出

    public <T> void commonExport(String fileName, List<T> data, Class<T> clazz, HttpServletResponse response) throws IOException {
        if (CollectionUtil.isEmpty(data)) {
            data = new ArrayList<>();
        }
        //設置標題
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
        EasyExcel.write(response.getOutputStream()).head(clazz).sheet("sheet1").doWrite(data);
    }

直接使用該方法可以作為公共的數(shù)據(jù)的導出接口

如果想要動態(tài)的下載任意一組數(shù)據(jù)怎么辦呢?可以使用這個方法

public void exportFreely(String fileName, List<List<Object>> data, List<List<String>> head, HttpServletResponse response) throws IOException {
        if (CollectionUtil.isEmpty(data)) {
            data = new ArrayList<>();
        }
        //設置標題
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
        EasyExcel.write(response.getOutputStream()).head(head).sheet("sheet1").doWrite(data);
    }

什么?不僅想一個接口展示全部的數(shù)據(jù)與信息,還要增加篩選條件?這個后期我可以單獨寫一篇文章解決這個問題。

到此這篇關于Springboot整合easyexcel實現(xiàn)一個接口任意表的Excel導入導出的文章就介紹到這了,更多相關Springboot Excel導入導出內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java中ResponseBodyEmitter的實現(xiàn)

    Java中ResponseBodyEmitter的實現(xiàn)

    這篇文章主要介紹了Java中ResponseBodyEmitter的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-03-03
  • Java數(shù)組添加元素實例

    Java數(shù)組添加元素實例

    這篇文章主要介紹了Java數(shù)組添加元素實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-09-09
  • 使用Mock進行業(yè)務邏輯層Service測試詳解

    使用Mock進行業(yè)務邏輯層Service測試詳解

    這篇文章主要介紹了使用Mock進行業(yè)務邏輯層Service測試詳解,mock是一種模擬對象的技術(shù),用于在測試過程中替代真實的對象,通過mock,我們可以控制被模擬對象的行為和返回值,以便進行更加精確的測試,需要的朋友可以參考下
    2023-08-08
  • 一篇文章帶你入門Java之編程規(guī)范

    一篇文章帶你入門Java之編程規(guī)范

    這篇文章主要介紹了如何養(yǎng)成良好java代碼編碼規(guī)范,規(guī)范需要平時編碼過程中注意,是一個慢慢養(yǎng)成的好習慣,下面小編就帶大家來一起詳細了解一下吧
    2021-08-08
  • Spring?Boot?中?@Value注解的使用示例詳解

    Spring?Boot?中?@Value注解的使用示例詳解

    @Value是Spring提供的一個注解,用于從配置文件或其他來源(如系統(tǒng)環(huán)境變量、SpEL表達式)中讀取值并注入到Bean的字段或方法參數(shù)中,這篇文章主要介紹了SpringBoot中@Value注解的使用,需要的朋友可以參考下
    2025-05-05
  • Java 8日期處理LocalDateTime時區(qū)轉(zhuǎn)換的3大誤區(qū)與解決方案

    Java 8日期處理LocalDateTime時區(qū)轉(zhuǎn)換的3大誤區(qū)與解決方案

    文章介紹了Java 8中的日期時間API,包括`LocalDateTime`、`ZonedDateTime`和`Instant`等核心類,以及它們在不同場景下的應用,文章還探討了常見誤區(qū),本文給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2025-12-12
  • 基于Apache組件分析對象池原理的實現(xiàn)案例分析

    基于Apache組件分析對象池原理的實現(xiàn)案例分析

    本文從對象池的一個簡單案例切入,主要分析common-pool2組件關于:池、工廠、配置、對象管理幾個角色的源碼邏輯,并且參考其在Redis中的實踐,對Apache組件分析對象池原理相關知識感興趣的朋友一起看看吧
    2022-04-04
  • MyBatis攔截器原理探究

    MyBatis攔截器原理探究

    MyBatis提供了一種插件(plugin)的功能,雖然叫做插件,但其實這是攔截器功能.這篇文章主要介紹了MyBatis攔截器原理探究,需要的朋友可以參考下
    2018-02-02
  • 無法解析MVC視圖的解決方案

    無法解析MVC視圖的解決方案

    文章介紹了當idea無法解析MVC視圖時的排查步驟,包括檢查依賴和配置文件,特別強調(diào)了檢查`template-loader-path`路徑配置,確保ftl文件位于該路徑下
    2026-01-01
  • Spring之兩種任務調(diào)度Scheduled和Async詳解

    Spring之兩種任務調(diào)度Scheduled和Async詳解

    這篇文章主要介紹了Spring之兩種任務調(diào)度Scheduled和Async,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10

最新評論

延寿县| 鹿邑县| 墨江| 油尖旺区| 中宁县| 库伦旗| 济源市| 西安市| 邢台市| 祁门县| 建水县| 会宁县| SHOW| 壶关县| 琼中| 扶风县| 卢湾区| 满城县| 凤山市| 应城市| 瓦房店市| 和顺县| 榕江县| 乐清市| 呼图壁县| 得荣县| 乌鲁木齐市| 福海县| 广汉市| 南开区| 远安县| 治多县| 黑山县| 漳平市| 临湘市| 县级市| 中江县| 响水县| 泗水县| 获嘉县| 凤台县|