jfinal layui easyexcel 这三样开源技术这里就不多介绍了,自行百度了解吧,他们的组合算是一个很高效又不失美观的操作体验。
操作主要分以下几步:
1、建立jfinal的操作环境,建议使用作者提供的demo , 创建一个 jfinal + undertow 的运行环境。undertow运行起来很快,不仅方便调试,而且运行稳定。https://www.jfinal.com/doc
2、引入easyexcel的相关包,我这里使用的是2.0.5版本,网上有很多1.2的版本教程,这里使用的最新的版本,同老版本还是有些区别的,作者提供了相关的demo,可以自己试一下。
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>test</scope>
</dependency>
3、建立你要导入的数据库表,我这里用的是mysql5.7,并编写你的导入模板。这里就不贴图了,自己亲手做下就行。
4、分别创建几个类文件 jfinal的controller 、service ,easyexcel使用的 excelData、excelDataListener 文件,当然还有前端访问的excelupload.html页面。
easyexcel 使用 Data文件来约束模板文件的标题(头文件),导入字段的类型和excel中cell的顺序
import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentRowHeight; import com.alibaba.excel.annotation.write.style.HeadRowHeight; import lombok.Data; /** * 基础数据类.用于基础数据表的导入 * 这里的排序和excel里面的排序一致 * * @author **/ @Data //使用注解来格式该文件 @HeadRowHeight(20) // 作为导出data的时候设置头文件行高 @ContentRowHeight(20) //作为导出data的时候设置内容行高 @ColumnWidth(25)//设置行宽 public class ExcelData { @ExcelProperty(value = "工号", index = 0) //这里的工号是和excel的标题一致,index 表示第几列数据 private String agentcode; @ExcelProperty(value = "姓名", index = 1) private String name; @ExcelProperty(value = "手机号", index = 2) private String mobile; @ExcelProperty(value = "身份证号", index = 3) private String idno; @ExcelProperty(value = "执业证号", index = 4) private String certifno; @ExcelProperty(value = "机构代码", index = 5) private String agentgroupcode; @ExcelProperty(value = "机构名称", index = 6) private String agentgroupname; public String getAgentcode() { return agentcode; } public void setAgentcode(String agentcode) { this.agentcode = agentcode; } public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile = mobile; } public String getIdno() { return idno; } public void setIdno(String idno) { this.idno = idno; } public String getCertifno() { return certifno; } public void setCertifno(String certifno) { this.certifno = certifno; } public String getAgentgroupcode() { return agentgroupcode; } public void setAgentgroupcode(String agentgroupcode) { this.agentgroupcode = agentgroupcode; } public String getAgentgroupname() { return agentgroupname; } public void setAgentgroupname(String agentgroupname) { this.agentgroupname = agentgroupname; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
easyexcel的ExcelDataListener 文件 是解析excel时候使用的监听,在读取一行数据的时候都要调用invoke()方法,在解析完excel之后执行 doAfterAllAnalysed() 方法,demo中提供了分批次处理的方法
public class ImBnExcelDataListener extends AnalysisEventListener<ImpBnExcelData> { private static final Log log = Log.getLog(ImBnExcelDataListener.class); /** * 每隔5条存储数据库,实际使用中可以500条,然后清理list ,方便内存回收 */ private static final int BATCH_COUNT = 500; List<ImpBnExcelData> list = new ArrayList<ImpBnExcelData>(); StringBuilder stringBuilder = new StringBuilder(); @Override public void invoke(ImpBnExcelData data, AnalysisContext context) { System.out.println("解析到一条数据:{}"+JSON.toJSONString(data)); list.add(data); /* list.add(data); if (list.size() >= BATCH_COUNT) { System.out.println("解析到一条数据:{}"+data.getAgentcode()); saveData(list); list.clear(); }else { saveLastData(list); list.clear(); }*/ //saveDataByOne(data); stringBuilder.append("("); //BnController BnController =new BnController(); String convertdata =convertToStr(data); stringBuilder.append(convertdata); stringBuilder.append("),"); } @Override public void doAfterAllAnalysed(AnalysisContext context) { String sqlpro = stringBuilder.toString().substring(0,stringBuilder.toString().lastIndexOf(",")); System.out.println(sqlpro); batchInsert(sqlpro); //saveDataObj(convertToObj(list)); System.out.println("所有数据解析完成!"); } public String convertToStr(ImpBnExcelData data){ String convertdata = "'"+data.getAgentcode()+"','" +data.getName()+"','" +data.getMobile()+"','" +data.getIdno()+"','" +data.getCertifno()+"','" +data.getAgentgroupcode()+"','" +data.getAgentgroupname()+"','" +DateUtil.getTodaySecNum()+"','" +DateUtil.getTodaySec()+"'"; return convertdata; } public static void batchInsert(String sqlpro) { long start = System.currentTimeMillis(); Config config = DbKit.getConfig("datasource"); Connection conn = null; try { conn = config.getConnection(); conn.setAutoCommit(false); StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("insert into table (xx,name,mobile,idno,xx,xx,xx,xx,uptime) values "); stringBuffer.append(sqlpro); PreparedStatement pst = conn.prepareStatement(stringBuffer.toString()); pst.addBatch(); pst.executeBatch(); conn.commit(); pst.close(); conn.close(); } catch (SQLException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("批量插入需要时间:"+(end - start)); //批量插入需要时间:24675 }
这里我使用了合成 insert value 值的方法,将读取的excel拼接成一条 insert into 语句,这样会大大提升批量存储效率,比一条条存要快的多,如果你的mysql接收sql语句的长度够,可以写成一条语句,如果不行就要分批进行存储,或者修改mysql.ini的 参数,将max_allowed_packet的值改大就行,这里我修改到了16M。 jfinal提供了多数据源多配置的方法可以使用 DbKit.getConfig() 或Db.use("")的方法直接使用数据源或直接调用jdbc,方便的不要不要的。
Controller 文件是jfinal用来做控制转发的文件,设置好路由之后,直接可以用Controller调用相关方法,在web环境下运行改方法。
public void upbnexcel() { AjaxMsg ajaxMsg = new AjaxMsg(); try { String webrootpath = PathKit.getWebRootPath(); //设置文件上传子目录 //String path = "uploads/excel/"; String path =PropKit.get("excel_upload_path"); UploadFile upload = getFile("file", webrootpath + File.separator + path); File file = upload.getFile(); //获取文件名 String extName = FileUtils.getFileExt(file.getName()); //获取文件上传的父目录 String filePath = upload.getUploadPath(); //时间命名文件 String fileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + extName; //重命名原来的文件 file.renameTo(new File(filePath + fileName)); long start = System.currentTimeMillis(); EasyExcel.read(filePath+fileName, ExcelData.class, new ExcelDataListener()).sheet().doRead(); log.info("导入耗时s:"+String.valueOf((System.currentTimeMillis()-start)/1000)); ajaxMsg.setState("success"); ajaxMsg.setMsg("上传成功,耗时"+String.valueOf((System.currentTimeMillis()-start)/1000)+"秒"); } catch (Exception e) { e.printStackTrace(); ajaxMsg.setState("fail"); ajaxMsg.setMsg("上传失败:"+e.getMessage()); } renderJson(ajaxMsg); }
通过web方法,先将excel上传到服务器的upload/excel文件夹下,并通过时间进行命名,然后直接调用 EasyExcel.read方法解析并写入数据库,然后通过ajaxMsg返回页面状态提示。这里有个注意
UploadFile upload = getFile("file", webrootpath + File.separator + path); 中的 "file" 是cos中的约束,必须要这么写,否则会上传失败
EasyExcel.read 提供了很多读取的方式,可以选取自己需要的方式进行调整。调整后别忘关闭操作流。
uploadexcel.html页面引入了layui相关框架,引入的包就不罗列了,直接写相关内容
<body> <div id="app" class="layui-form"> <div class="container"> <div class="layui-form-item"> <a class="layui-btn layui-btn-warm" href="模板.xlsx" target="_blank">模板下载</a> </div> <blockquote class="layui-elem-quote"> <form class="layui-form" action=""> <div class="layui-form-item"> <div class="layui-inline"> <div class="layui-upload"> <button type="button" class="layui-btn layui-btn-normal" id="file">选择文件</button> <button type="button" class="layui-btn" id="updo">开始上传</button> </div> </div> </div> </form> </blockquote> </div> </div> </body>
<script src="../../../static/plugins/layui/layui.js"></script> <script> //一般直接写在一个js文件中 layui.use(['layer', 'form', 'upload'], function () { var layer = layui.layer , form = layui.form , upload = layui.upload; //选完文件后不自动上传 var uploadInst = upload.render({ elem: '#file' , url: '/upexcel' , auto: false , accept: 'file' //普通文件 //,multiple: true , bindAction: '#updo' , done: function (res) { //上传完毕回调 if (res.state == "success") { parent.layer.alert(res.msg); } else { parent.layer.alert(res.msg); return false; } } }); }) }
下载操作比较简单,注意一下 ajax不能直接下载文件,需要通过 action 或 href 来下载 ,这里我做了两步请求,先通过选择列表中需要下载的项,然后执行id序列化操作,然后再次执行查询和下载操作,才能保存下载的excel文件。
$("#expdata").on('click', function () { var checkStatus = table.checkStatus('tablelist'), data = checkStatus.data; var ids = []; for (var i = 0; i < data.length; i++) { ids.push(data[i].id); } console.log(ids); $.ajax({ //type: 'post', url: '/downExcel', data: {ids: ids}, success: function (response) { console.log(response.idstr); window.location.href="/expExcel?idstr="+response.idstr; }, }); });
/** * 导出excel */ public void expExcel(){ try { //String[] ids = getParaValues("ids[]"); String idstr = getPara("idstr"); HttpServletResponse response=getResponse(); 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=demo.xlsx"); EasyExcel.write(response.getOutputStream(), ExcelData.class).sheet("模板").doWrite(downExceldata(idstr)); // EasyExcel.write(fileName, ImpBnExcelData.class).sheet("模板").doWrite(data()); }catch (Exception e){ } renderNull(); //renderFile(file,"demo.xlsx"); }
//下载时候首先执行该方法,并将选中的id拆分组合为字符串,再将字符串进行回传 public void downExcel(){ String[] ids = getParaValues("ids[]"); String idstr = Arrays.toString(ids); idstr = idstr.substring(1, idstr.length() - 1); renderJson("idstr",idstr); } public List<ImpBnExcelData> downExceldata(String idstr){ List<Record> lists =BnService.me().findByIdstr(idstr); List<ImpBnExcelData> bndatalist = new ArrayList<>(); for(Record record : lists) { ImpBnExcelData bnExcelData = new ImpBnExcelData(); ExcelData.setAgentcode(record.getStr("agentcode")); ExcelData.setAgentgroupcode(record.getStr("agentgroupcode")); ExcelData.setAgentgroupname(record.getStr("agentgroupname")); ExcelData.setCertifno(record.getStr("certifno")); ExcelData.setIdno(record.getStr("idno")); ExcelData.setMobile(record.getStr("mobile")); ExcelData.setName(record.getStr("name")); datalist.add(bnExcelData); } return bndatalist; }