在使用Spring Boot框架开发系统时,难免会碰到excel导入导出的功能,本文将借助EasyExcel包,实现excel数据的导入导出功能。
1.实现思路
1.定义导入数据的model
在里面添加校验注解和导入列的字段名,本文使用java自带的校验注解。使用分组验证,统一为数据导入数据库接口。
2.编写监听器
在监听器里面实现数据的校验、设置导入数据的默认值、模版数据与数据库实体数据的转换。
3.编写导入拓展类
在里面实现excel导入功能和模板下载功能。
4.定义数据导入数据库接口
使得excel导入功能和spring bean有关联,在相应的service接口中实现该接口。
5.定义excel导入工厂类
每次从工厂获得监听器对象和excel导入拓展对象。
2.具体步骤
1.导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.2.0</version>
</dependency>
2.编写model类
本文使用javax.validation包下的注解进行简单校验,如果业务复杂,则可以导入Hibernate Validator包,@ExcelProperty注解是EasyExcel包自带注解,用来确定导入excel表格的列名,groups则为分组参数。
@Data
public class UserImportModel{
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空", groups = {ExcelImportService.class})
@ExcelProperty(value = "用户名")
private String userName;
/**
* 联系电话
*/
@NotBlank(message = "联系电话不能为空", groups = {ExcelImportService.class})
@ExcelProperty(value = "联系电话")
private String phone;
}
2.编写监听器类
监听器类中主要定义了数据校验、设置默认值、数据转换方法,该方法在使用时可根据实际业务需要重写。转换后的数据均存储在dataList列表,可通过getDataList获取,在初始化监听器时需要传入数据校验器,泛型M对应模型类,E对应实体类,源码附上。
public class ReadDataListener<M, E> implements ReadListener<M> {
/**
* 数据列表
*/
private List<E> dataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 验证器
*/
private final Validator validator;
/**
* 当前处理行号
*/
private long currentRowNo = 1;
private static final Logger LOGGER = LoggerFactory.getLogger(TianHengServiceImpl.class);
/**
* 获取当前行号
*
* @return long 行号
*/
public long getCurrentRowNo() {
return currentRowNo;
}
/**
* 构造方法
* 每次创建Listener的时候需要把spring管理的类传进来
*
*/
public ReadDataListener(Validator validator) {
this.validator = validator;
}
/**
* 解析数据
*
* @param data 单行记录
* @param context 解析上下文
*/
@Override
public void invoke(M data, AnalysisContext context) {
// 处理数据
E entity = handleData(data);
// 添加数据
dataList.add(entity);
currentRowNo++;
}
/**
* 所有数据解析完成了 就会来调用
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
LOGGER.info("所有数据解析完成!");
}
/**
* 数据预处理,可转化实体类中的字典数据,也可以设置默认字段
*
* @param vo 视图对象
*/
public E handleData(M vo) {
// 数据校验
validateData(vo);
// 设置默认值
setDefaultValue(vo);
// 数据转换
return convertData(vo);
}
private void validateData(M model) {
Set<ConstraintViolation<M>> violations = validator.validate(model, ExcelImportService.class);
if (!violations.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (ConstraintViolation<M> violation : violations) {
sb.append(violation.getMessage()).append("\n");
}
throw new OuterException(RetCode.BAD_REQUEST, sb.toString());
}
}
/**
* 设置默认值
*
* @param model 模型对象
*/
protected void setDefaultValue(M model) {
// 如无需设置,则该方法可为空
}
/**
* 转换数据
*
* @param model 模型对象
* @return {@link E} 数据库实体对象
*/
protected E convertData(M model) {
throw new InnerException(RetCode.INTERNAL_SERVER_ERROR,"未实现convertData方法");
}
public List<E> getDataList() {
return dataList;
}
}
3.编写导入拓展类
该类实现了excel导入功能,以及模板下载功能,泛型M对应模型Model类,E对应数据库实体类。
初始化时需要传入保存数据的spring Bean,导入监听器,导入模板地址。
downloadImportTemplate方法
为下载导入模板,通过模版地址,获取模板输入流,将输入流拷贝到response输出流中,再设置响应头等信息。
importExcel
excel数据导入功能,做excel文件类型校验,获取文件输入流,通过EasyExcel.read(inputStream, modelClass, this.readListener).sheet().doRead();方法读取excel数据,数据会被添加到监听器中的dataList中,在catch里面做了异常捕获,可详细捕获异常数据所在的行。最后调用所传入spring bean的saveAll方法,保存所有数据到数据库中。
public class ExcelImportExtension<M, E>{
/**
* 数据监听器
*/
private ReadDataListener readListener;
/**
* 业务接口
*/
private final ExcelImportService<E> service;
/**
* 导入模板
*/
private String importTemplate;
public ExcelImportExtension(ExcelImportService<E> service, ReadDataListener<M, E> readDataListener, String templatePath) {
this.service = service;
this.readListener = readDataListener;
this.importTemplate = templatePath;
}
public void setReadListener(ReadDataListener readListener) {
this.readListener = readListener;
}
/**
* 设置导入模板
*/
protected void setImportTemplate(String importTemplate) {
this.importTemplate = importTemplate;
}
/**
* 下载导入模板
*/
public void downloadImportTemplate(HttpServletResponse response) {
if (!StringUtils.hasText(importTemplate)) {
throw new OuterException(RetCode.BAD_REQUEST,"请设置导入模板");
}
ClassPathResource classPathResource = new ClassPathResource(importTemplate);
try (InputStream inputStream = classPathResource.getInputStream();
OutputStream outputStream = response.getOutputStream()) {
// 设置响应信息
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码
String fileName = URLEncoder.encode(importTemplate, "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
IOUtils.copy(inputStream, outputStream);
} catch (Exception exception) {
throw new OuterException(RetCode.BAD_REQUEST,"下载导入模板失败");
}
}
/**
* excel数据导入
* @param file 上传的文件
* return 是否导入成功
*/
@Transactional(rollbackFor = Exception.class)
public Boolean importExcel(MultipartFile file, Class<M> modelClass) {
String[] excelType = {"xls", "xlsx"};
if (file.isEmpty()) {
throw new OuterException(RetCode.BAD_REQUEST, "文件不能为空");
}
String filename = file.getOriginalFilename();
assert filename != null;
if (!filename.endsWith(excelType[0]) && !filename.endsWith(excelType[1])) {
throw new OuterException(RetCode.BAD_REQUEST, "excel文件格式不正确");
}
try(InputStream inputStream = file.getInputStream()) {
EasyExcel.read(inputStream, modelClass, this.readListener).sheet().doRead();
} catch (Exception exception) {
long currentRowNo = this.readListener.getCurrentRowNo();
Throwable throwable = exception;
while (throwable.getCause() != null) {
throwable = throwable.getCause();
}
throw new OuterException(RetCode.BAD_REQUEST,"第"+currentRowNo+"条数据错误,"+throwable.getMessage());
}
//保存所有数据
return service.saveAll(this.readListener.getDataList());
}
4.定义数据导入数据库接口
该接口用于将转换好的数据保存到数据库,使用excel导入功能的bean必须实现该接口。
public interface ExcelImportService<T> {
/**
* 批量保存数据到数据库
* @param items 批量数据
* @return 保存结果
*/
Boolean saveAll(List<T> items);
}
5.定义导入工厂
工厂支持获取拓展类对象以及监听器对象,在获取监听器对象时,要传入类型转换方法,针对不同的模板和实体有不同的转换方法,所有没有设置默认转换方法。
public class ImportFactory {
/**
* 创建Excel拓展器
* @param excelImportService 导入器
* @param <M> 导入模型类
* @param <E> 数据库实体数据类型
*@return ExcelImportExtension<M, E> 导入器扩展类
*/
public static <M, E>ExcelImportExtension createExcelImportExtension(ExcelImportService excelImportService,
ReadDataListener readDataListener,
String templatePath) {
return new ExcelImportExtension<M, E>(excelImportService, readDataListener, templatePath){};
}
/**
* 创建读取数据监听器
* @param validator 验证器
* @param function 转换函数
* @return 读取数据监听器
* @param <M> 导入模型类
* @param <E> 数据库实体数据类型
*/
public static <M, E> ReadDataListener<M, E> createReadDataListener(Validator validator, Function<M, E> function) {
return new ReadDataListener<M, E>(validator) {
@Override
public E convertData(M model) {
return function.apply(model);
}
};
}
}
使用实例
其中,saveAll方法实现了ExcelImportService<DutyEntity>接口中的saveAll方法,实现数据批量入库。
pulic class UserServiceImpl implement UserService,ExcelImportService<UserEntity> {
private final UserDao userDao;
private final Validator validator;
public UserServiceImpl(UserDao userDao, Validator validator) {
this.userDao = userDao;
this.validator = validator;
}
@Override
public Boolean importData(MultipartFile file) {
ReadDataListener<UserImportModel, UserEntity> readListener = ImportFactory.createReadDataListener(validator, this::modelToEntity);
ExcelImportExtension excelImportExtension = ImportFactory
.createExcelImportExtension(this, readListener, "用户导入模板.xlsx");
excelImportExtension.setReadListener(readListener);
return excelImportExtension.importExcel(file, UserImportModel.class);
}
@Override
public void downloadTemplate(HttpServletResponse response) {
ReadDataListener readListener = ImportFactory.createReadDataListener(validator, this::modelToEntity);
ExcelImportExtension<UserImportModel, UserEntity> excelImportExtension = new ExcelImportExtension<UserImportModel,
UserEntity>(this, readListener,"用户导入模板.xlsx"){};
excelImportExtension.downloadImportTemplate(response);
}
@Override
public Boolean saveAll(List<UserEntity> items) {
if (CollectionUtils.isEmpty(items)) {
throw new OuterException(RetCode.BAD_REQUEST, "导入数据不能为空");
}
int batchSize = 100;
boolean flag = true;
for (int i = 0; i < items.size(); i += batchSize) {
List<UserEntity> subList = items.subList(i, Math.min(i + batchSize, items.size()));
flag = userDao.insertBatch(subList);
if (!flag) {
return flag;
}
}
return flag;
}
/**
* 模型转实体
*
* @return 实体
*/
private UserEntity modelToEntity(UserImportMoel model) {
UserEntity userEntity = new UserEntity;
BeanUtils.copyProperties(userEntity, model);
return userEntity;
}
}
以上就是excel数据导入的全部实例及其代码了,有不足之处,还请见谅。