一次導入2w行的表格,並通過註解校驗字段

背景

最近項目中有一個需求,需要一次通過excel導入大量的數據。之前用的easypoi測試需要大約一小時才能完成,這樣明顯是不行的。
深入瞭解之後,其實只需要將批式處理改爲流式處理便可以解決解析慢的問題。綜合考慮後選用了easyexcel這個框架。
測試2W行的數據從導入到入庫大約需要5s,大大加快了速度。 後續增加了HibernateValidator進行校驗數據。

代碼

web導入:

@RestController
@RequestMapping("easyexcel")
public class EasyExcelTest  {
    @Autowired
    private ExecutiveInfoService executiveInfoService;

    @PostMapping("import_excel")
   public Object importExcel(@RequestParam("file") MultipartFile file) throws IOException {
        UploadDataListener uploadDataListener = new UploadDataListener(executiveInfoService);
        EasyExcel.read(file.getInputStream(), ExecutiveInfoExcel.class,  uploadDataListener).sheet().doRead();
        // 這裏根據自己的返回類型去修改
        if (uploadDataListener.getInsertFlag()){
            return "success";
        }
        return uploadDataListener.getFailList();
    }

    @GetMapping("download")
    public void download(HttpServletResponse response) throws IOException {
        // 這裏注意 有同學反應使用swagger 會導致各種問題,請直接用瀏覽器或者用postman
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        // 這裏URLEncoder.encode可以防止中文亂碼 當然和easyexcel沒有關係
        String fileName = URLEncoder.encode("zms", "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
        EasyExcel.write(response.getOutputStream(), ExecutiveInfoExcel.class).sheet("模板").doWrite(null);
    }
}

解析表格DTO:

@Data
public class ExecutiveInfoExcel  {

    @ExcelProperty("零售客戶名")
    @NotBlank
    private String executiveName;
    @ExcelProperty("企業名稱")
    @NotBlank
    private String enterpriseName;
    @ExcelProperty("統一社會信用代碼")
    @NotNull(message = "不能爲空" )
    private String enterpriseCreditCode;
    @ExcelProperty("詳細地址")
    @NotBlank
    private String enterpriseAddress;
    @ExcelProperty("零售客戶號")
    @NotNull
    private Long executiveId;
    @ExcelProperty("零售客戶身份")
    @NotBlank
    private String executivePosition;
    @ExcelProperty("註冊資金")
    @NotBlank
    private String registerCapital;
    @ExcelProperty("法定代表人")
    @NotBlank
    private String legalRepresentative;
    @ExcelProperty("電話")
    @NotBlank
    private String telephone;
    @ExcelProperty("郵箱")
    @NotBlank
    private String email;
    @ExcelProperty("公司類型")
    @NotBlank
    private String companyType;
    @ExcelProperty("所屬行業")
    @NotBlank
    private String industry;
    @ExcelProperty("卡號")
    @NotNull
    private Integer cardNumber;
    @ExcelProperty("身份證號碼")
    @NotBlank
    private String idCard;
    @ExcelProperty("客戶號開立日期")
    @DateTimeFormat("yyyy-MM-dd")
    private Date customerOpeningDay;
    @ExcelProperty("聯繫電話")
    @NotNull
    private Integer telephoneNumber;
    @ExcelProperty("成立時間")
    @DateTimeFormat("yyyy-MM-dd")
    private Date establishmentTime;
    @ExcelProperty("最高持卡級別")
    @NotBlank
    private String highestCardholderLevel;
    @ExcelProperty("是否辦理過小微貸款業務")
    @NotBlank
    private String flagMicroLoanBusiness;

}

解析監聽類


public class  UploadDataListener extends AnalysisEventListener<ExecutiveInfoExcel> {
    private static final Logger LOGGER =
            LoggerFactory.getLogger(UploadDataListener.class);

    private ExecutiveInfoService executiveInfoService;
    private Boolean insertFlag = true;
    private List<Integer> failList = new ArrayList<>();

    List<ExecutiveInfoExcel> list = new ArrayList<ExecutiveInfoExcel>();

    /**
     * 如果使用了spring,請使用這個構造方法。每次創建Listener的時候需要把spring管理的類傳進來
     *
     * @param
     */
    public UploadDataListener(ExecutiveInfoService executiveInfoService) {
        this.executiveInfoService = executiveInfoService;
    }

    /**
     * 這個每一條數據解析都會來調用
     *
     * @param data
     *            one row value. Is is same as {@link #()}
     * @param context
     */
    @Override
    public void invoke(ExecutiveInfoExcel data, AnalysisContext context) {
        //LOGGER.info("解析到一條數據:{}", JSON.toJSONString(data));
        Validator validator = HibernateValidator.getValidator();
        Set<ConstraintViolation<ExecutiveInfoExcel>> validateSet = validator.validate(data,Default.class);
        if (validateSet != null && !validateSet.isEmpty()) {
            //System.out.println("校驗出錯:" + data);
            LOGGER.info("校驗出錯: {}",data);
            failList.add(context.readRowHolder().getRowIndex());
            insertFlag = false;
        }else {
            list.add(data);
        }

    }

    /**
     * 所有數據解析完成了 都會來調用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 這裏也要保存數據,確保最後遺留的數據也存儲到數據庫
       if (insertFlag){
           saveData();
       }else {
           LOGGER.info("有校驗錯誤不入庫");
       }
    }

    @Override
    public void onException(Exception exception, AnalysisContext context){
        LOGGER.error("exception: {}",exception.toString());
        context.currentReadHolder();
    }

    @Override
    public void invokeHead(Map<Integer, CellData> var1, AnalysisContext var2){

    }

    /**
     * 加上存儲數據庫
     */
    private void saveData() {
       LOGGER.info("{}條數據,開始存儲數據庫!", list.size());
        List<ExecutiveInfoEntity> entityList = ConvertUtils.sourceToTarget(list, ExecutiveInfoEntity.class);
        executiveInfoService.insert(entityList);
       // LOGGER.info("存儲數據庫成功!");
    }

    public List<Integer> getFailList() {
        return failList;
    }

    public Boolean getInsertFlag() {
        return insertFlag;
    }
}

異步插入數據庫,具體的實體類這裏省略

  public void insert(List<ExecutiveInfoEntity> entityList) {
        //一次插入3000條數據到數據庫
        int num = 1;
        asynRedisService.testzms(entityList);
        int toIndex=num;
        int listSize = entityList.size();
        for (int i=0; i< entityList.size(); i+=num){
            if (i+num > listSize){
                toIndex = listSize-i;
            }
            List<ExecutiveInfoEntity> infoEntities = entityList.subList(i, i+toIndex);
            LOGGER.info("入庫{}", i);
            executiveInfoDao.insertOrUpdate(infoEntities);
        }
    }

開啓校驗工具類

public class HibernateValidator {
    private static Validator validator = Validation.byProvider( org.hibernate.validator.HibernateValidator.class )
            .configure()
            .addProperty( "hibernate.validator.fail_fast", "true" )
            .buildValidatorFactory().getValidator();

    private HibernateValidator() {

    }

    public static Validator getValidator() {
        return validator;
    }
}

類型轉換工具類

public class ConvertUtils {
    private static Logger logger = LoggerFactory.getLogger(ConvertUtils.class);

    public static <T> T sourceToTarget(Object source, Class<T> target){
        if(source == null){
            return null;
        }
        T targetObject = null;
        try {
            targetObject = target.newInstance();
            BeanUtils.copyProperties(source, targetObject);
        } catch (Exception e) {
            logger.error("convert error ", e);
        }

        return targetObject;
    }

    public static <T> List<T> sourceToTarget(Collection<?> sourceList, Class<T> target){
        if(sourceList == null){
            return null;
        }

        List targetList = new ArrayList<>(sourceList.size());
        try {
            for(Object source : sourceList){
                T targetObject = target.newInstance();
                BeanUtils.copyProperties(source, targetObject);
                targetList.add(targetObject);
            }
        }catch (Exception e){
            logger.error("convert error ", e);
        }

        return targetList;
    }
}

項目地址:git地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章