0-编辑历史
2023-10-27 14:02,
宜网购 需求池都满了,请加速处理
1-参考资料
easyexcel.opensource.alibaba.com/
2-前情概要
在最近的项目开发中遇到了从Excel文档中导入数据,从数据库中导出数据至Excel表格的需求,因此了解了一下在Spring应用程序中处理Excel表格的相关技术,最终选择阿里开源的EasyExcel库来实现。
这个项目的基础是使用Spring Boot开发的一个RESTful Web Services,使用Mybatis-Plus作为持久层框架,数据库使用H2内存数据库,使用Gradle作为构建工具。
3-导入依赖
// https://mvnrepository.com/artifact/com.alibaba/easyexcel
implementation group: 'com.alibaba', name: 'easyexcel', version: '3.3.2'
4-Excel表格内容
有名称、品牌、价格、介绍、评分5列
5-实体类
上述Excel表格对应实体类如下所示,体现了Java 对象中的字段与 Excel 表格中的列之间的映射关系
@Data
@TableName(value = "coffee")
@JsonIgnoreProperties(ignoreUnknown=true)
@ExcelIgnoreUnannotated
public class Coffee {
@TableId(value = "ID", type = IdType.AUTO)
private Integer id;
@ExcelProperty(value = "名称")
@TableField(value = "name")
private String name;
@ExcelProperty(value = "品牌")
@TableField(value = "brand")
private String brand;
@ExcelProperty(value = "价格")
@TableField(value = "price")
private Double price;
@ExcelProperty(value = "介绍")
@TableField(value = "introduction")
private String introduction;
@ExcelProperty(value = "评分")
@TableField(value = "rating")
private Double rating;
}
@ExcelIgnoreUnannotated 注解是 EasyExcel 提供的一个注解,用于标记在读取Excel文件时是否忽略未标记 @ExcelProperty 注解的字段。当我们使用 EasyExcel 读取 Excel 文件时,如果某个字段没有标记 @ExcelProperty 注解,则默认情况下会抛出异常。如果我们希望忽略这些未标记注解的字段,可以在类上添加 @ExcelIgnoreUnannotated 注解。
@ExcelProperty 注解是 EasyExcel 提供的另一个注解,用于标记 Java 对象中的字段与 Excel 表格中的列之间的映射关系。通过 @ExcelProperty 注解,我们可以指定 Java 对象中的哪个字段对应 Excel 表格中的哪一列,以及如何将 Excel 中的数据转换为 Java 对象中的数据。
6-从Excel表格导入数据
6-1-读取Excel表格监听器
/**
* 有个很重要的点 CoffeeExcelDataListener 不能被spring管理
* 要每次读取excel都要new,然后里面用到spring可以构造方法传进去
*
* @author
* @version 1.0
* @since 2023 /10/25 16:05
*/
@Slf4j
public class CoffeeExcelDataListener implements ReadListener<Coffee> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<Coffee> cachedCoffeeDataList = new ArrayList<>(BATCH_COUNT);
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private CoffeeService coffeeService;
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param coffeeService
*/
public CoffeeExcelDataListener(CoffeeService coffeeService) {
this.coffeeService = coffeeService;
}
/**
* When analysis one row trigger invoke function.
*
* @param data one row value. It is same as {@link AnalysisContext#readRowHolder()}
* @param context analysis context
*/
@Override
public void invoke(Coffee data, AnalysisContext context) {
try {
ObjectMapper objectMapper = new ObjectMapper();
String coffeeString = objectMapper.writeValueAsString(data);
log.info("解析到一条数据:{}", coffeeString);
} catch (Exception e) {
e.printStackTrace();
}
cachedCoffeeDataList.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedCoffeeDataList.size() >= BATCH_COUNT) {
coffeeService.saveBatch(cachedCoffeeDataList);
// 存储完成清理 list
cachedCoffeeDataList = new ArrayList<>(BATCH_COUNT);
}
}
/**
* if have something to do after all analysis
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
coffeeService.saveBatch(cachedCoffeeDataList);
log.info("所有数据解析完成!");
}
}
6-2-服务接口
public interface CoffeeService extends IService<Coffee> {
List<Coffee> importFromExcel(MultipartFile multipartFile) throws IOException;
}
6-3-服务实现类代码
@Service
public class CoffeeServiceImpl extends ServiceImpl<CoffeeMapper, Coffee> implements CoffeeService {
@Override
public List<Coffee> importFromExcel(MultipartFile multipartFile) throws IOException {
List<Coffee> coffeeList = new ArrayList<>();
InputStream inputStream = multipartFile.getInputStream();
try {
ExcelReader excelReader = EasyExcel.read(inputStream, Coffee.class, new CoffeeExcelDataListener(this)).build();
// 构建一个sheet 这里可以指定名字或者no
ReadSheet readSheet = EasyExcel.readSheet(0).head(Coffee.class).build();
// 读取一个sheet
excelReader.read(readSheet);
} catch (Exception e) {
log.error("导入失败" , e);
}
return coffeeList;
}
}
6-4-控制器接口
使用 @RequestPart 注解处理HTTP请求参数, @RequestPart 注解用于处理多部分请求中的文件或二进制数据。它通常与 multipart/form-data 类型的请求一起使用。
@PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) 的作用是指定了处理 POST 请求的方法,并要求请求的 Content-Type 为 multipart/form-data,以便正确解析和处理多部分表单数据
@RestController
@RequestMapping("/coffee")
public class CoffeeController {
@Autowired
private CoffeeService coffeeService;
@PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "Import coffees from Excel")
public ResponseEntity<List<Coffee>> importCoffeeExcelFile(@RequestPart(value = "file") MultipartFile file) throws IOException {
if (file.isEmpty()) {
return ResponseEntity.badRequest()
.body(Collections.emptyList());
}
try {
List<Coffee> coffeeList = coffeeService.importFromExcel(file);
return ResponseEntity.ok(coffeeList);
} catch (IOException e) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Collections.emptyList());
}
}
}
7-导出数据至Excel表格
7-1-服务接口
public interface CoffeeService extends IService<Coffee> {
void exportToExcel(HttpServletResponse httpServletResponse) throws IOException;
}
7-2-服务实现类接口
@Service
public class CoffeeServiceImpl extends ServiceImpl<CoffeeMapper, Coffee> implements CoffeeService {
@Override
public void exportToExcel(HttpServletResponse httpServletResponse) throws IOException {
List<Coffee> coffeeList = this.list();
// 响应文件名称
String fileName = "coffee.xlsx";
httpServletResponse.reset();
httpServletResponse.setContentType("application/vnd.ms-excel");
httpServletResponse.setCharacterEncoding("utf-8");
// UTF-8
httpServletResponse.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
// 设置响应头
httpServletResponse.setHeader("Content-disposition", "attachment;filename=" + fileName);
try {
OutputStream outputStream = httpServletResponse.getOutputStream();
ExcelWriter excelWriter = EasyExcel.write(outputStream).build();
WriteSheet writeSheet = EasyExcel.writerSheet(0, "Sheet1").head(Coffee.class).build();
excelWriter.write(coffeeList, writeSheet);
excelWriter.finish();
} catch (Exception e) {
log.error("导出失败" , e);
}
}
}
HttpServletResponse 的作用是处理 HTTP 响应。它提供了一组方法,使开发人员能够设置响应的状态码、响应头、响应体等内容。通过HttpServletResponse,开发人员可以向客户端发送数据、文件、重定向请求等,并控制响应的行为和格式。它在 Web 开发中起到了关键的作用,用于构建和发送 HTTP 响应给客户端。
7-3-控制器接口
@RestController
@RequestMapping("/coffee")
public class CoffeeController {
@Autowired
private CoffeeService coffeeService;
@GetMapping("/export")
@Operation(summary = "Export coffees to Excel")
public void exportCoffeeExcelFile(HttpServletResponse httpServletResponse) throws Exception {
coffeeService.exportToExcel(httpServletResponse);
}
}
8-处理Excel工具库介绍
Apache POI:Apache POI是一个流行的Java库,用于处理Microsoft Office格式的文件,包括Excel。它提供了丰富的API,可以读取、写入和操作Excel文件。你可以使用poi和poi-ooxml模块来处理不同版本的Excel文件。
EasyExcel:EasyExcel是阿里巴巴开源的一个简单易用的Java库,专注于Excel的导入和导出。它提供了简洁的API,支持大量数据的快速导入和导出,并具有较低的内存占用。EasyExcel还支持注解方式配置导入导出的映射关系。
JExcelApi:JExcelApi是另一个流行的Java库,用于读取、写入和操作Excel文件。它提供了一组简单的API,使得处理Excel变得更加容易。然而,JExcelApi的开发已经停止,并且对于新版本的Excel文件支持不完整。
9-总结
这篇文章在一个Spring RESTful WebService的基础上进一步开发,添加了处理Excel的功能,使用EasyExcel库实现导入Excel和导出Excel。当然,这都是最简单的,作为个人学习记录。后续还可以在此基础上做深入开发。