EasyExcel學習
一、EasyExcel簡介
一、EasyExcel是什么
EasyExcel是一個基于Java的簡單、省內存的讀寫Excel的阿里開源項目。在盡可能節約內存的情況下支持讀寫百M的Excel。
官網:https://easyexcel.opensource.alibaba.com/
學習Easyexcel前需要了解導入和導出是什么意思:
導入:一般我們會把數據從excel到數據庫的過程稱為導入!
導出:一般我們會把數據從數據庫到excel的過程稱為導出!
1.2、 EasyExcel 能用在哪里
項目中涉及到Excel文件,CVS文件大多數的讀寫操作,均可以使用!
1.3、為什么要選用EasyExcel解析excel
二、EasyExcel的使用
2.1、快速入門
首先創建項目,并配置maven。
導入easyexcel的依賴:
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.2</version>
</dependency>
創建一個實體類Employee,與excel表格對應:
/*** 與excel文件相對應的模型類*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {@ExcelProperty("員工編號")private int id;@ExcelProperty("員工姓名")private String name;@ExcelProperty("入職日期")private Date date;@ExcelProperty("員工工資")private double salary;}
這里需要一個工具類,用戶獲取到模塊這一級的磁盤路徑:
/*** 獲取代碼路徑的工具類,可以獲取到模塊這一級的磁盤路徑;*/
public class TestFileUtil {public static String getPath() {return TestFileUtil.class.getResource("/").getPath().replace("classes/","");}
}
簡單寫excel:
首先需要準備測試數據:
// 準備測試數據的方法
private List<Employee> data(int count) {List<Employee> list = ListUtils.newArrayList();for (int i = 1; i <= count; i++) {list.add(new Employee(i,"測試數據"+i,new Date(),6.6*i));}return list;
}
實現寫操作:
@Test
public void write(){//文件的路徑和名字String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";EasyExcel.write(fileName, Employee.class).sheet("模板666").doWrite(data(10)); //sheet:表格名字
}
到對應的路徑下打開excel,查看測試結果:
簡單讀excel:
讀操作需要傳入監聽器,這里使用EasyExcel提供的PageReadListener:
@Test
public void read(){String fileName = TestFileUtil.getPath() + "simpleWrite1750300831958.xlsx";EasyExcel.read(fileName, Employee.class, new PageReadListener<Employee>(dataList -> {//讀取數據后對數據執行的操作,這里直接輸出for (Employee demoData : dataList) {System.out.println(demoData);}})).sheet().doRead();
}
在控制臺查看測試結果:
2.2、EasyExcel進階操作
批量寫數據:
這里寫100萬數據,每次寫1萬,執行100次:
// 批量寫數據 100萬
@Test
public void write(){String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";try (ExcelWriter excelWriter = EasyExcel.write(fileName, Employee.class).build()) {WriteSheet writeSheet = EasyExcel.writerSheet("測試數據").build();long t1 = System.currentTimeMillis();for (int i = 0; i < 100; i++) {List<Employee> data = data(10000);excelWriter.write(data, writeSheet);}long t2 = System.currentTimeMillis();//測試執行效率:共耗時10秒System.out.println("共耗時" + (t2-t1)/1000 + "秒"); }
}
測試結果顯示寫100萬數據共耗時10秒。
按模版填充單個對象數據:
如果上面寫的數據格式不好看怎么辦?
可以利用阿里提供的模版寫數據,模版設置的樣式新添加的數據會自動包含樣式。
可以使用{}
來填充數據:
如果傳入的不是單個對象,而是一個集合可以在字段前面加一個點就可以:
首先,需要準備一個execl模版:
// 批量寫數據 100萬
@Test
public void write(){//分多次填充,會使用文件緩存(省內存)String fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx";String templateFileName = TestFileUtil.getPath() + "模版.xlsx";try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {WriteSheet writeSheet = EasyExcel.writerSheet().build();long t1 = System.currentTimeMillis();for (int i = 0; i < 100; i++) {excelWriter.fill(data(10000), writeSheet);}long t2 = System.currentTimeMillis();System.out.println(t2-t1); //測試執行效率}
}
測試結果:
自定義監聽器讀海量數據:
自定義監聽器讀數據:
/*** 自定義監聽器讀數據*/
public class EmployeeListener implements ReadListener<Employee> {private int count = 100; //緩存量private ArrayList<Employee> list = new ArrayList<>(count);private EmployeeDao dao;public EmployeeListener(EmployeeDao dao) {this.dao = dao;}/*** 每讀一行數據,都會調用這個方法*/@Overridepublic void invoke(Employee employee, AnalysisContext analysisContext) {// 將讀取到的一行數據添加到集合list.add(employee);// 判斷是不是到達緩存量了if(list.size()>=100){// 操作數據庫dao.save(list);// 清空緩存list= new ArrayList<>(count);}}/*** 讀完整個excel之后,會調用這個方法* 最后list中還會存在元素,執行這個方法處理剩余的元素*/@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {if(list.size()>0){// 操作數據庫dao.save(list);list= new ArrayList<>(count);}}}
這里只是模擬了一下操作數據庫的操作:
/*** 模擬操作數據庫*/
public class EmployeeDao {public void save(List<Employee> list){System.out.println(list.size()+"模擬操作數據庫......");}
}
讀取海量數據:
/*** 自定義監聽器,讀海量數據*/
public class ManyRead {@Testpublic void read(){String fileName = TestFileUtil.getPath()+"repeatedWrite1750302016738.xlsx";ExcelReader reader = EasyExcel.read(fileName, Employee.class, new EmployeeListener(new EmployeeDao())).build();ReadSheet sheet = EasyExcel.readSheet().build();reader.read(sheet);}}
測試,在控制臺查看輸出結果!
2.3、EasyExcel綜合應用
需求:
-
實現文件導入功能。
-
實現文件導出功能。
功能實現:
- 配置持久層配置,并引入相關依賴:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///easyexcel?useSSL=false&useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=newpass
- 創建實體類Employee:
/*** 員工實體類*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {@ExcelProperty("員工工號")private int id;@ExcelProperty("員工姓名")private String name;@ExcelProperty("員工工資")private double salary;@ExcelProperty("入職日期")private Date date;}
- 創建工具類TestFileUtil來獲取文件路徑:
/*** 獲取代碼路徑的工具類,可以獲取到模塊這一級的磁盤路徑;*/
public class TestFileUtil {public static String getPath() {return TestFileUtil.class.getResource("/").getPath().replace("classes/","");}public static void main(String[] args) {System.out.println(getPath());}}
- 創建持久層EmployeeMapper:
/*** 持久層*/
public interface EmployeeMapper {/*** 批量插入數據*/void beathInsert(@Param("list") List<Employee> list);/*** 查詢數據*/@Select("select * from employee")@ResultType(Employee.class)List<Employee> getData();}
- 創建EmployeeMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmployeeMapper"><!--批量插入數據--><insert id="beathInsert">insert into employee (id,name,salary,date) values<foreach collection="list" item="demoData" separator=",">(null,#{demoData.name},#{demoData.salary},#{demoData.date})</foreach></insert></mapper>
- 創建接口層EmployeeService:
/*** Service接口層*/
public interface EmployeeService {public List<Employee> getData();public void addData(List<Employee> list);}
- 創建業務實現類EmployeeServiceImpl:
/*** 業務層*/
@Service
public class EmployeeServiceImpl implements EmployeeService {@Autowiredprivate EmployeeMapper dao;@Overridepublic List<Employee> getData() {return dao.getData();}@Overridepublic void addData(List<Employee> list) {dao.beathInsert(list);}}
- 創建監聽器EmployeeListener:
public class EmployeeListener implements ReadListener<Employee> {private int count = 10000;private EmployeeService dao ;private List<Employee> list = new ArrayList<>(count);public EmployeeListener(EmployeeService dao) {this.dao = dao;}@Overridepublic void invoke(Employee employee, AnalysisContext analysisContext) {list.add(employee);if(list.size()>=count){dao.addData(list);list = new ArrayList<>(count);}}@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {if(list.size()>0){dao.addData(list);}}}
- 創建控制層MyController:
@Controller
@RequestMapping("/")
public class MyController {@Autowiredprivate EmployeeService service;/*** Excel讀操作*/@RequestMapping("/upload")@ResponseBodypublic void upload(MultipartFile file, HttpServletResponse response) throws IOException {long t1 = System.currentTimeMillis();//Excel讀操作EasyExcel.read(file.getInputStream(), Employee.class, new EmployeeListener(service)).sheet().doRead();long t2 = System.currentTimeMillis();response.setContentType("text/html;charset=utf-8");response.getWriter().print("導入數據成功,共用時:"+(t2-t1));}/*** Excel寫操作*/@RequestMapping("/download")public void download(HttpServletResponse response) throws IOException {//設置Excel文件下載的類型response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 這里URLEncoder.encode可以防止中文亂碼 當然和easyexcel沒有關系String fileName = URLEncoder.encode("測試666", "UTF-8").replaceAll("\\+", "%20");//Content-disposition:可以控制文件是直接在瀏覽器中顯示還是作為附件下載,這里是直接作為附件下載,文件名字是fileName.xlsxresponse.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");//Excel寫操作EasyExcel.write(response.getOutputStream(), Employee.class).sheet("模板").doWrite(service.getData());}}
- 測試:
前端頁面:
導入100萬條數據的excel,查看數據庫:
可以看到,導入100萬數據僅僅耗時26s:
并且內存占用為20多兆: