SpringBoot+Mybatis-plus整合easyExcel批次匯入Excel到資料庫+匯出Excel

2023-01-11 15:04:48

一、前言

今天小編帶大家一起整合一下easyExcel,之所以用這個,是因為easyExcel效能比較好,不會報OOM

市面上常見的匯入匯出Excel分為三種:

  • hutool
  • easyExcel
  • poi

hutooleasyExcel都是對poi的封裝,使用起來更加方便!
如果想使用poihutool匯出的可以看一下小編的之前寫的文章:

使用POI+hutool匯入Excel
使用POI把查詢到的資料表資料匯出到Excel中,一個表一個sheet

匯出的話看一下這篇,下面主要以匯入來展開介紹!
EasyExcel匯出Excel表格到瀏覽器,並通過Postman測試匯出Excel

二、匯入依賴

小編這裡是3.0.X版本的,版本不同可能導致部分有出入,如果大家版本是3.1.X,可以去官方檔案看看有不一樣的!

官方檔案

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.0.5</version>
</dependency>

三、實體類

這裡可以自帶的轉換器:

  • @DateTimeFormat("yyyy年MM月dd日HH時mm分ss秒")
  • LocalDateTimeStringConverter

或者自定義轉化器:
實現:implements Converter<T>

具體檔案:官方檔案

@ExcelProperty引數注意:

這裡不建議 index 和 name 同時用,要麼一個物件只用index,要麼一個物件只用name去匹配

用名字去匹配,這裡需要注意,如果名字重複,會導致只有一個欄位讀取到資料

/**
 * @author wangzhenjun
 * @date 2022/12/2 15:52
 */
@Data
public class Test {

    @TableId
    private Integer id;
    @ExcelProperty(index = 0)
    private String name;
    @ExcelProperty(index = 1)
    private Integer age;
    @ExcelProperty(index = 2,converter = LocalDateTimeStringConverter.class)
    private LocalDateTime time;
}

四、編寫監聽器

注意點:
這個監聽器一定不要是單例的,被spring管理預設為單例,如果要使用@Component,一定要加上:
@Scope("prototype"),這樣在建立完後spring不會進行管理,每次都會是新bean!
不加@Component在匯入時要進行new ImportDataListener
小編這裡不想new了直接這樣寫!!如果不想這樣,可以使用構造器set進行使用!
BATCH_COUNT :資料閾值,超過了就會清理list,在之前可以進行儲存到資料庫中,方便記憶體回收,防治OOM
這裡儲存到資料庫中一般使用批次儲存,不要解析到一行就去儲存資料庫中,這樣資料量大會給資料庫增加IO,導致掛掉!這裡小編使用ServiceImplsaveBatch()方法,也可以自己寫一下,像小編這樣寫,會出現迴圈依賴,加上@Lazy就行!

/**
 * @author wangzhenjun
 * @date 2022/12/2 15:38
 */
@Slf4j
@Component
// 每次bean都是新的,不要單例
@Scope("prototype")
public class ImportDataListener implements ReadListener<Test> {

    @Autowired
    @Lazy
    private TestService testService;
    
    /**
     * 每隔5條儲存資料庫,實際使用中可以100條,然後清理list ,方便記憶體回收
     */
    private static final int BATCH_COUNT = 100;
    /**
     * 快取的資料
     */
    private List<Test> importExcelDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);


    /**
     * 這個每一條資料解析都會來呼叫
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(Test data, AnalysisContext context) {
        log.info("解析到一條資料:{}", JSON.toJSONString(data));
        importExcelDataList.add(data);
        // 達到BATCH_COUNT了,需要去儲存一次資料庫,防止資料幾萬條資料在記憶體,容易OOM
        if (importExcelDataList.size() >= BATCH_COUNT) {
            saveData();
            // 儲存完成清理 list
            importExcelDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    /**
     * 所有資料解析完成了 都會來呼叫
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 這裡也要儲存資料,確保最後遺留的資料也儲存到資料庫
        saveData();
        log.info("所有資料解析完成!");
    }

    /**
     * 加上儲存資料庫
     */
    private void saveData() {
        log.info("{}條資料,開始儲存資料庫!", importExcelDataList.size());
        testService.saveBatch(importExcelDataList);
        log.info("儲存資料庫成功!");
    }
}

五、Controller

/**
 * @author wangzhenjun
 * @date 2022/10/26 16:51
 */
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private TestService testService;

    @PostMapping("/import")
    public Result importExcel(@RequestBody MultipartFile multipartFile){
        testService.importExcel(multipartFile);
        return Result.success("ok");
    }
}

六、Service

/**
 * @author wangzhenjun
 * @date 2022/10/26 16:55
 */
public interface TestService extends IService<Test> {
    void importExcel(MultipartFile multipartFile);
}

七、ServiceImpl

/**
 * @author wangzhenjun
 * @date 2022/10/26 16:56
 */
@Service
public class TestServiceImpl extends ServiceImpl<TestDbMapper, Test> implements TestService{

    @Autowired
    private ImportDataListener importDataListener;

    @SneakyThrows
    @Override
    public void importExcel(MultipartFile multipartFile) {
        InputStream inputStream = multipartFile.getInputStream();
        // 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet 檔案流會自動關閉
        EasyExcel.read(inputStream, Test.class, importDataListener).sheet().doRead();
    }
}

八、Mapper

/**
 * @author wangzhenjun
 * @date 2022/10/26 17:07
 */
public interface TestDbMapper extends BaseMapper<Test> {
}

九、測試

準備Excel資料:

postman上傳:


控制檯列印:

資料庫檢視:

完美搞定!!

十、總結

這樣就完成了easyExcel批次匯入Excel到資料庫,還是有很多要注意的點:

  • 自定義轉換器
  • 監聽器不要單例
  • 儲存資料庫採用批次
  • 版本差距

如果對你有幫助,還請不要吝嗇您的發財小手,你的一鍵三連是我寫作的動力,謝謝大家哈!!


可以看下小編的微信公眾號,文章首發看,歡迎關注,一起交流哈!!