近期有個需求,說是要用到excel導入導出,通常咱們的想法都是按照行數,因而實現了,後面發現公司需求的是列讀,甚至不規則的單個excel的讀。因而就用poi本身寫了按照單元格讀的實現。
想起了以前用到的poi,通過搜索發現,開源的項目中有比較好的封裝poi的框架,一個是阿里出的easyExcel,另外一個是easypoi,感受使用起來都很方便。網上說easyExcel能解決大文件內存溢出問題,因而項目中就使用easyExcel了。前端
簡單普及下easyExcel原理,不作底層碼農,瞭解點上層設計有好處:java
寫有大量數據的xlsx文件時,POI爲咱們提供了SXSSFWorkBook類來處理,這個類的處理機制是當內存中的數據條數達到一個極限數量的時候就flush這部分數據,再依次處理餘下的數據,這個在大多數場景可以知足需求。
讀有大量數據的文件時,使用WorkBook處理就不行了,由於POI對文件是先將文件中的cell讀入內存,生成一個樹的結構(針對Excel中的每一個sheet,使用TreeMap存儲sheet中的行)。
若是數據量比較大,則一樣會產生java.lang.OutOfMemoryError: Java heap space錯誤。POI官方推薦使用「XSSF and SAX(event API)」方式來解決。
分析清楚POI後要解決OOM有3個關鍵.
根據上面官網給的信息,咱們得有個模型來接收每行的數據,本例用CommonUser
對象,該對象上在這上面也能夠加數據校驗,還須要個解析每一個行的監聽器CommonUserListener
,能夠來處理每行的數據,而後進行數據庫操做讀寫。spring
來個小demo(用的mybatis-plus框架)sql
controler數據庫
@RestController @RequestMapping("info/commonuser") public class CommonUserController { @Autowired private CommonUserService commonUserService; /** * excel導入(按照行讀) * <p> * 1. 建立excel對應的實體對象 參照{@link CommonUser} * <p> * 2. 因爲默認一行行的讀取excel,因此須要建立excel一行一行的回調監聽器,參照{@link CommonUserListener} * <p> * 3. 直接讀便可 */ @PostMapping("upload") @ResponseBody public String upload(MultipartFile file) throws IOException { EasyExcel.read(file.getInputStream(), CommonUser.class, new CommonUserListener(commonUserService)) .sheet() .doRead(); return "success"; } /** * 文件下載(失敗了會返回一個有部分數據的Excel) * <p> * 1. 建立excel對應的實體對象 參照{@link CommonUser} * <p> * 2. 設置返回的 參數 * <p> * 3. 直接寫,這裏注意,finish的時候會自動關閉OutputStream,固然你外面再關閉流問題不大 */ @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("用戶表", "UTF-8"); response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); EasyExcel.write(response.getOutputStream(), CommonUser.class).sheet("模板").doWrite(data()); } /** * excel導出 * 文件下載而且失敗的時候返回json(默認失敗了會返回一個有部分數據的Excel) * * @since 2.1.1 */ @GetMapping("downloadFailedUsingJson") public void downloadFailedUsingJson(HttpServletResponse response) throws IOException { // 這裏注意 有同窗反應使用swagger 會致使各類問題,請直接用瀏覽器或者用postman try { response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); // 這裏URLEncoder.encode能夠防止中文亂碼 固然和easyexcel沒有關係 String fileName = URLEncoder.encode("測試", "UTF-8"); response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); // 這裏須要設置不關閉流 EasyExcel.write(response.getOutputStream(), CommonUser.class).autoCloseStream(Boolean.FALSE).sheet("模板") .doWrite(data()); } catch (Exception e) { // 重置response response.reset(); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); Map<String, String> map = new HashMap<String, String>(); map.put("status", "failure"); map.put("message", "下載文件失敗" + e.getMessage()); response.getWriter().println(new Gson().toJson(map)); } } private List<CommonUser> data() { List<CommonUser> list = commonUserService.list(); return list; } }
CommonUserService (和讀excel無關,業務須要)json
/** * 用戶Service * * @author hfl 690328661@qq.com * @date 2020-05-16 08:42:50 */ public interface CommonUserService extends IService<CommonUser> { }
CommonUserServiceImpl瀏覽器
@Service("commonUserService") public class CommonUserServiceImpl extends ServiceImpl<CommonUserMapper, CommonUser> implements CommonUserService { private final static Logger logger = LoggerFactory.getLogger(CommonUserServiceImpl.class); }
CommonUserListener(負責獲取每行的數據,而後根據須要進行db保存)安全
public class CommonUserListener extends AnalysisEventListener<CommonUser> { private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class); /** * 每隔5條存儲數據庫,實際使用中能夠3000條,而後清理list ,方便內存回收 * mybatis-plus 默認1000 */ private static final int BATCH_COUNT = 1000; List<CommonUser> list = new ArrayList<>(); /** * 假設這個是一個DAO,固然有業務邏輯這個也能夠是一個service。固然若是不用存儲這個對象沒用。 */ private CommonUserService commonUserService; public CommonUserListener(CommonUserService commonUserService) { // 這裏是demo,因此隨便new一個。實際使用若是到了spring,請使用下面的有參構造函數 this.commonUserService = commonUserService; } /** * 每隔5條存儲數據庫,實際使用中能夠3000條,而後清理list ,方便內存回收 */ @Override public void invoke(CommonUser data, AnalysisContext context) { LOGGER.info("解析到一條數據:{}", new Gson().toJson(data)); list.add(data); // 達到BATCH_COUNT了,須要去存儲一次數據庫,防止數據幾萬條數據在內存,容易OOM if (list.size() >= BATCH_COUNT) { saveData(); // 存儲完成清理 list list.clear(); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { // 這裏也要保存數據,確保最後遺留的數據也存儲到數據庫 saveData(); LOGGER.info("全部數據解析完成!"); } /** * 加上存儲數據庫 */ private void saveData() { LOGGER.info("{}條數據,開始存儲數據庫!", list.size()); commonUserService.saveBatch(list); LOGGER.info("存儲數據庫成功!"); } }
CommonUserMapper(數據庫操做的)微信
/** * 用戶表 * * @author hfl * @date 2020-05-16 08:42:50 */ @Mapper public interface CommonUserMapper extends BaseMapper<CommonUser> { }
實體對象mybatis
@Data @TableName("common_user") public class CommonUser extends BaseEntity { /** * 用戶ID */ @TableId private String userId; /** * 用戶名 */ @ExcelProperty("字符串標題") private String userName; /** * 真實姓名 */ private String userRealname; /** * 密碼 */ private String userPassword; /** * 鹽 */ private String userSalt; /** * 手機號碼 */ private String userMobile; /** * 性別 */ private String userSex; /** * 頭像url */ private String userAvatar; /** * 電子郵箱 */ private String userEmail; /** * 帳號狀態(0-正常,1-凍結) */ private Integer userStatus; /** * 擴展字段 */ private String userEx; @Override public String toString() { return "CommonUser{" + "userId='" + userId + '\'' + ", userName='" + userName + '\'' + ", userRealname='" + userRealname + '\'' + ", userPassword='" + userPassword + '\'' + ", userSalt='" + userSalt + '\'' + ", userMobile='" + userMobile + '\'' + ", userSex='" + userSex + '\'' + ", userAvatar='" + userAvatar + '\'' + ", userEmail='" + userEmail + '\'' + ", userStatus=" + userStatus + ", userEx='" + userEx + '\'' + '}'; } }
數據庫表結構:
CREATE TABLE `common_user` ( `user_id` varchar(32) NOT NULL COMMENT '用戶ID', `user_name` varchar(50) DEFAULT NULL COMMENT '用戶名', `user_realname` varchar(50) DEFAULT NULL COMMENT '真實姓名', `user_password` varchar(50) DEFAULT NULL COMMENT '密碼', `user_salt` varchar(50) DEFAULT NULL COMMENT '鹽', `user_mobile` varchar(20) DEFAULT NULL COMMENT '手機號碼', `user_sex` varchar(20) DEFAULT NULL COMMENT '性別', `user_avatar` varchar(255) DEFAULT NULL COMMENT '頭像url', `user_email` varchar(50) DEFAULT NULL COMMENT '電子郵箱', `user_status` tinyint(1) DEFAULT NULL COMMENT '帳號狀態(0-正常,1-凍結)', `user_ex` text COMMENT '擴展字段', `creater` varchar(32) DEFAULT NULL COMMENT '建立者', `modifier` varchar(32) DEFAULT NULL COMMENT '修改者', `create_time` datetime DEFAULT NULL COMMENT '建立時間', `update_time` datetime DEFAULT NULL COMMENT '更新時間', `is_deleted` tinyint(1) DEFAULT NULL COMMENT '0:未刪除,1:刪除', PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用戶表';
啓動項目:
使用postman先把本地的數據庫數據導出到excel中試試:
瀏覽器中輸入: localhost:7000/info/commonuser/downloadFailedUsingJson
能夠看出數據庫中的被導出來了: 標題頭和實體中的@ExcelProperty對應:這裏只寫了字符串標題這個表頭,其餘的會默認顯示英文字段。
將剛纔數據庫中的數據所有刪除:導入剛纔的Excel數據試試
使用post測試:
能夠看到控制已經顯示成功,
看下數據庫:顯示已經按照行讀,並寫入數據庫中了
上面的導入excel數據是按照行讀的,可是個人需求是這樣的:
列形式,不少個sheet都不同,怎麼處理呢?
因而想到,easyExcel確定是實現不了了,乾脆使用poi自帶的按照單元格本身去讀。
思路: 每一個模板excel對應一個實體,每一個單元格的位置(行號和列號)在實體上經過註解對應好,這樣我解析單元格,取出每一個單元格的值,和位置,賦值給對應的實體的屬性。
由於easyExcel默認引用poi,因此不須要引maven包,直接寫就行了:
@Test public void AllExcelRead() throws IOException, InvalidFormatException { //一、指定要讀取EXCEL文檔名稱 String filename="C:\\Users\\69032\\Desktop\\vechicleService.xlsx"; //二、建立輸入流 FileInputStream input=new FileInputStream(filename); //三、經過工做簿工廠類來建立工做簿對象 Workbook workbook= WorkbookFactory.create(input); //四、獲取工做表 (能夠按照sheet名字,也能夠按照sheet的索引) String sheetName = "車輛信息備案申請"; Sheet sheet=workbook.getSheet(sheetName); // Sheet sheet=workbook.getSheet("工做表1"); // Sheet sheet = workbook.getSheetAt(0); //五、獲取行 Row row=sheet.getRow(53); //六、獲取單元格 Cell cell=row.getCell(2); //七、讀取單元格內容 System.out.println(cell.getStringCellValue()); }
運行結果以下:
完美找到excel對應單元格的數據:
定義個屬性上的註解,指定某個單元格的屬性:
RecordTemplate
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RecordTemplate { //行號 int rowNo(); //列號 int columnNo(); //是否必填 FillCommentEnum comment(); //單元格名稱 String name(); //數據類型 }
是否必填的枚舉:
FillCommentEnum
/** * Title: FillCommentEnum * Description: 單元格 填充枚舉 * * @author hfl * @version V1.0 * @date 2020-05-29 */ public enum FillCommentEnum { FILL(0, "必填"), EMPTY(1, "非必填"); private int code; private String description; FillCommentEnum(int code, String description) { this.code = code; this.description = description; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
接下來,我須要定義實體和模板對應。
@Data @TableName("vehicle_static_info") public class VehicleStaticInfo extends BaseEntity { /** * 車輛ID(主鍵) */ @TableId private String vehicleId; /** * 最大基準扭矩 */ private String engineMaxTorque; /** * 車牌號碼((GB17691-2005必填,GB17691-2018選填)) * 此字段,數據庫中已經移除,提取到車輛公共信息表中了 */ @RecordTemplate(rowNo = 3, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車牌號碼") @TableField(exist = false) private String vehicleLicense; /** * 車牌顏色(車牌顏色) */ @RecordTemplate(rowNo = 4, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車牌顏色") private String licenseColor; /** * 車體結構((編碼見說明3)) */ @RecordTemplate(rowNo = 5, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車體結構") private Integer vehicleStructure; /** * 車輛顏色 */ @RecordTemplate(rowNo = 6, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車輛顏色") private String vehicleColor; /** * 覈定載重 */ @RecordTemplate(rowNo = 7, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "覈定載重") private Double vehicleLoad; /** * 車輛尺寸mm(長)(32960、17691等) */ @RecordTemplate(rowNo = 8, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車輛尺寸mm(長)") private Double vehicleLong; /** * 車輛尺寸mm(寬)(國六,新能源等字典) */ @RecordTemplate(rowNo = 9, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車輛尺寸mm(寬)") private Double vehicleWide; /** * 車輛尺寸mm(高) */ @RecordTemplate(rowNo = 10, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車輛尺寸mm(高)") private Double vehicleHigh; /** * 總質量 */ @RecordTemplate(rowNo = 11, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "總質量") private Double grossVehicleMass; /** * 車輛類型((編碼見說明1)) */ @RecordTemplate(rowNo = 12, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車輛類型(編碼見右面1)") private Integer vehicleType; /** * 行業類型((編碼見說明8 http://www.stats.gov.cn/tjsj/tjbz/hyflbz/201905/P020190716349644060705.pdf)) */ @RecordTemplate(rowNo = 13, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "行業類型(編碼見右面8)") private String industryType; /** * 車輛型號((GB17691-2005必填,GB17691-2018選填)) */ @RecordTemplate(rowNo = 14, columnNo = 2, comment = FillCommentEnum.FILL, name = "車輛型號(GB17691-2005必填,GB17691-2018選填)") @NotEmpty(message = "車輛型號不能爲空", groups = {ImportGroup.class}) private String vehicleModel; /** * 購買時間 */ @RecordTemplate(rowNo = 15, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "購買時間") private String buyingDate; /** * 車架號VIN((必填)) */ @RecordTemplate(rowNo = 16, columnNo = 2, comment = FillCommentEnum.FILL, name = "車架號VIN(必填)") @NotEmpty(message = "車架號VIN不能爲空", groups = {ImportGroup.class}) @TableField(exist = false) private String vehicleFrameNo; /** * 行駛證號 */ @RecordTemplate(rowNo = 17, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "行駛證號") private String drivingLicenseNo; /** * 發動機型號((GB17691-2005必填,GB17691-2018選填)) */ @RecordTemplate(rowNo = 18, columnNo = 2, comment = FillCommentEnum.FILL, name = "發動機型號(GB17691-2005必填,GB17691-2018選填)") @NotEmpty(message = "發動機型號不能爲空", groups = {ImportGroup.class}) private String engineModel; /** * 發動機編號 */ @RecordTemplate(rowNo = 19, columnNo = 2, comment = FillCommentEnum.FILL, name = "發動機編號") @NotEmpty(message = "發動機編號不能爲空", groups = {ImportGroup.class}) private String engineNo; /** * 車籍地 */ @RecordTemplate(rowNo = 20, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車籍地") private String vehiclePlace; /** * 車輛技術等級((編碼見說明2)) */ @RecordTemplate(rowNo = 21, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車輛技術等級(編碼見右面2)") private Integer technicalLevel; /** * 出廠日期 */ @RecordTemplate(rowNo = 22, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "出廠日期") private String productionDate; /** * 等級評定日期 */ @RecordTemplate(rowNo = 23, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "等級評定日期") private String gradeAssessmentDate; /** * 二級維護日期 */ @RecordTemplate(rowNo = 24, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "二級維護日期") private String twoMaintenanceDate; /** * 二級維護狀態((編碼見說明5)) */ @RecordTemplate(rowNo = 25, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "二級維護狀態(編碼見右面5)") private Integer twoMaintenanceStatus; /** * 年審狀態((編碼見說明4)) */ @RecordTemplate(rowNo = 26, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "年審狀態(編碼見右面4)") private Integer yearEvaluationStatus; /** * 年檢有效期 */ @RecordTemplate(rowNo = 27, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "年檢有效期") private String yearinspectionPeriod; /** * 保險有效期 */ @RecordTemplate(rowNo = 28, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "保險有效期") private String insurancePeriod; /** * 保養有效期 */ @RecordTemplate(rowNo = 29, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "保養有效期") private String maintenancePeriod; /** * 所屬單位名稱 */ @RecordTemplate(rowNo = 30, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "所屬單位名稱") private String enterpriseName; /** * 車輛聯繫人 */ @RecordTemplate(rowNo = 31, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車輛聯繫人") private String contactsName; /** * 車輛聯繫電話 */ @RecordTemplate(rowNo = 32, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車輛聯繫電話") private String contactPhone; /** * 車輛sim卡號 */ @RecordTemplate(rowNo = 33, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車輛sim卡號") private String terminalSim; /** * 車輛註冊時間 */ @RecordTemplate(rowNo = 34, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車輛註冊時間") private String registerDate; /** * 所屬組織ID */ @RecordTemplate(rowNo = 35, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "所屬組織ID") private String organizationId; /** * 環保局車輛類型((編碼見說明6)) */ @RecordTemplate(rowNo = 36, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "環保局車輛類型(編碼見右面6)") private Integer epaVehicleType; /** * 運輸局車輛類型((編碼見說明7)) */ @RecordTemplate(rowNo = 37, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "運輸局車輛類型(編碼見右面7)") private Integer transVehicleType; /** * 全部綁定的sim卡 */ @RecordTemplate(rowNo = 38, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "全部綁定的sim卡") private String terminalAllSim; /** * 全部者地址 */ @RecordTemplate(rowNo = 39, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "全部者地址") private String ownerAddress; /** * 車牌型號 */ @RecordTemplate(rowNo = 40, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車牌型號") private String licenseModel; /** * 行政區劃 */ @RecordTemplate(rowNo = 41, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "行政區劃") private String administrativeArea; /** * 行政地址 */ @RecordTemplate(rowNo = 42, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "行政地址") private String administrativeAddress; /** * 總客數 */ @RecordTemplate(rowNo = 43, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "總客數") private Integer totalnumberGuest; /** * 整備質量 */ @RecordTemplate(rowNo = 44, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "整備質量") private Double curbWeight; /** * 列車最大總質量 */ @RecordTemplate(rowNo = 45, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "列車最大總質量") private Double maximumTotalMassOfTrain; /** * 入網證號 */ @RecordTemplate(rowNo = 46, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "入網證號") private String netNumber; /** * 初次登記日期 */ @RecordTemplate(rowNo = 47, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "初次登記日期") private String initialRegistrationDate; /** * 年檢日期 */ @RecordTemplate(rowNo = 48, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "年檢日期") private String annualInspectionDate; /** * 強制報廢日期 */ @RecordTemplate(rowNo = 49, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "強制報廢日期") private String mandatoryScrapDate; /** * 所屬企業簡稱 */ @RecordTemplate(rowNo = 50, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "所屬企業簡稱") private String enterpriseShortName; /** * 車輛SN */ @RecordTemplate(rowNo = 51, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "車輛SN") private String vehicleSn; /** * 安全芯片型號((車載終端含有安全芯片的必填)) */ @RecordTemplate(rowNo = 52, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "安全芯片型號(車載終端含有安全芯片的必填)") private String chipType; /** * 車載終端型號((必填)) */ @RecordTemplate(rowNo = 53, columnNo = 2, comment = FillCommentEnum.FILL, name = "車載終端型號(必填)") @NotEmpty(message = "車載終端型號不能爲空", groups = {ImportGroup.class}) private String tboxType; /** * 激活模式((編碼見說明9,必填)0-無需激活,1-須要激活) */ @RecordTemplate(rowNo = 54, columnNo = 2, comment = FillCommentEnum.FILL, name = "激活模式(編碼見右面9,必填)") @NotEmpty(message = "激活模式不能爲空", groups = {ImportGroup.class}) private Integer vehRegisterMode; /** * 排放水平((編碼見說明10,必填)(1-國六,2-國五,3-國四,4-國三,5-國二,6-排氣後處理系統改裝車輛)) */ @RecordTemplate(rowNo = 55, columnNo = 2, comment = FillCommentEnum.FILL, name = "排放水平(編碼見右面10,必填)") @NotEmpty(message = "放水平不能爲空", groups = {ImportGroup.class}) private Integer emissionlLevelType; /** * * 整車生產企業 */ @RecordTemplate(rowNo = 56, columnNo = 2, comment = FillCommentEnum.FILL, name = "整車生產企業") @NotEmpty(message = "整車生產企業不能爲空", groups = {ImportGroup.class}) private String vehicleFirm; /** * 安全芯片編號(備案不須要) */ private String chipCode; /** * 備註 */ private String remark; /** * 車輛備案結果(0:草稿;1:待審覈;2:未備案 3:審覈經過4:審覈未經過) * @return */ @TableField(exist = false) private Integer recordResult;
思路: 遍歷實體類上的有@ExcelTemplate註解的屬性,有的話說明事由單元格和它對應的,把該屬性的行號和列號傳遞給 解析單元格數據的方法,返回單元格的值,再經過java的 Filed的反射機制,給類賦值,就獲取了excel中全部值了。在解析單元格的時候根據是否必填,也能夠提早拋出異常。
解析單元格的相關方法:
/** * 獲取sheet對象 */ public static Sheet getSheetByStream(InputStream inputStream, String sheetName) { Workbook workbook = null; try { workbook = WorkbookFactory.create(inputStream); } catch (Exception e) { throw new ServiceException("excel文件有誤"); } Sheet sheet = null; if (StringUtils.isBlank(sheetName)) { //取第一個 sheet = workbook.getSheetAt(0); } else { sheet = workbook.getSheet(sheetName.trim()); } return sheet; } /** * 讀取單個單元格的值 * * @param sheet * @param name * @param rowNo * @param columnNo */ public static String readCell(Sheet sheet, int rowNo, int columnNo, FillCommentEnum comment, String name) { //五、獲取行 Row row = sheet.getRow(rowNo); //六、獲取單元格 Cell cell = row.getCell(columnNo); //七、讀取單元格內容 String stringCellValue = getCellValueByType(cell, name); if (comment.getCode() == 0 && StringUtils.isBlank(stringCellValue)) { throw new ServiceException(name + "不能爲空"); } logger.info(stringCellValue); return stringCellValue; } public static String getCellValueByType(Cell cell,String name){ String cellValue = ""; if(cell.getCellTypeEnum() == CellType.NUMERIC){ if (HSSFDateUtil.isCellDateFormatted(cell)) { cellValue = DateFormatUtils.format(cell.getDateCellValue(), "yyyy-MM-dd"); } else { NumberFormat nf = NumberFormat.getInstance(); cellValue = String.valueOf(nf.format(cell.getNumericCellValue())).replace(",", ""); } logger.info(cellValue); }else if(cell.getCellTypeEnum() == CellType.STRING){ cellValue = String.valueOf(cell.getStringCellValue()); }else if(cell.getCellTypeEnum() == CellType.BOOLEAN){ cellValue = String.valueOf(cell.getBooleanCellValue()); }else if(cell.getCellTypeEnum() == CellType.ERROR){ cellValue = "錯誤類型"; throw new ServiceException("單元格"+name+": "+cellValue); } return cellValue; }
反射給對象賦值的相關方法:
/** * 解析excel備案數據到對象 * * @return * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException */ public static Object parseExcelToModel(String className, Sheet sheet) throws ClassNotFoundException, IllegalAccessException, InstantiationException { Class<?> clazz = Class.forName(className); Field[] declaredFields = clazz.getDeclaredFields(); Object o = clazz.newInstance(); //獲取excel的流,前端傳入 for (Field field: declaredFields) { field.setAccessible(true); if (field.isAnnotationPresent(RecordTemplate.class)) { RecordTemplate annotation = field.getAnnotation(RecordTemplate.class); Class<?> type = field.getType(); logger.info(type.getName()); //單元格的值 String value = ReadExcellCellUtils.readCell(sheet, annotation.rowNo(), annotation.columnNo(), annotation.comment(), annotation.name()); //經過反射把 單元格的值 給對象屬性 setFieldValue(o, field, type, value); } } return o; } /** * 經過反射把 單元格的值 給對象屬性 * @param o * @param field * @param type * @param value * @throws IllegalAccessException */ private static void setFieldValue(Object o, Field field, Class<?> type, String value) throws IllegalAccessException { Object targetValue = null; if (StringUtils.isEmpty(value)) { return; } if (type.equals(Integer.class)) { targetValue = Integer.parseInt(value); } else if (type.equals(Double.class)) { targetValue = Double.parseDouble(value); } else if (type.equals(Float.class)) { targetValue = Float.parseFloat(value); } else if (type.equals(Boolean.class)) { targetValue = Boolean.getBoolean(value); }else{ targetValue = value; } field.set(o, targetValue); } /** * 解析數據到model * * @param className 類名 * @param inputStream 輸入流 * @param sheetName sheetname 能夠爲null,爲null時取第一頁 * @return 映射對象 */ public static Object parseExcelToModel(String className, InputStream inputStream, String sheetName) { Sheet sheetByStream = getSheetByStream(inputStream, sheetName); try { return parseExcelToModel(className, sheetByStream); } catch (Exception e) { e.printStackTrace(); throw new ServiceException("解析數據到model失敗"); } }
提供這3個值就能夠了。
舉例說明:
測試:
使用postman請求: excel的數據都獲取到了:
自此,按照單元格的例子就完成了,這種適合不規則的excel,並且行數肯定,特別是針對模板excel的讀都是很通用的。
總結:本文主要實現了easyExcel按照行數的例子,還有本身封裝的針對通用不規則excel按照列(確切是按照單元格)讀的例子。若有問題或者錯誤,歡迎留言討論。
我的微信公衆號:
搜索: 盛開de每一天
不定時推送相關文章,期待和你們一塊兒成長!!
完
感謝點贊和收藏,轉發請註明文章地址和做者名稱。