<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
</dependency>
基于上述两个原则,代码实现如下,示例中的POJO是PersonPO:
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import com.spring.accumulator.io.excel.GenderConverter;
import lombok.NoArgsConstructor;
import lombok.Data;
/**
* (Person)表实体类
*
* @author wangrubin
* @since 2022-07-15 18:22:45
*/
@Data
@NoArgsConstructor
@TableName("person")
public class PersonPO implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
@ExcelIgnore
private Long id;
@ExcelProperty(value = "姓名")
private String name;
@ExcelProperty(value = "性别", converter = GenderConverter.class)
private Integer male;
@ExcelProperty(value = "年龄")
private Integer age;
}
为了实现通用的Excel导入工具,本文设计了一个批量插入接口,用于批量插入数据到数据库,而非多次逐条插入。
import java.util.List;
/**
* 批量插入的Mapper, 用xml配置文件自定义批量插入,
* 避免MyBatis的逐条插入降低性能
*
* @param <T>
* @author wangrubin
* @date 2022-08-02
*/
public interface BatchInsertMapper<T> {
void batchInsert(List<T> list);
}
import java.util.List;
/**
* (Person)表数据库访问层
*
* @author wangrubin
* @since 2022-07-15 18:22:45
*/
@Mapper
public interface PersonMapper extends BaseMapper<PersonPO>, BatchInsertMapper<PersonPO> {
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.spring.accumulator.dao.PersonMapper">
<insert id="batchInsert" parameterType="list">
insert into wangrubin_db.person
(name, age, male)
values
<foreach collection="list" item="item" index="index" separator=",">
(
#{item.name},
#{item.age},
#{item.male}
)
</foreach>
</insert>
</mapper>
在PersonPO中,我们用1,0表示男,女;但是在Excel文件中,用汉字"男"和"女"替代1和0,所以需要进行转换。
/**
* Excel性别列对应的转换器
*
* @author wangrubin
* @date 2022-08-02
*/
public class GenderConverter implements Converter<Integer> {
@Override
public Class<?> supportJavaTypeKey() {
return Integer.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 这里读的时候会调用,将Excel中的字段汉字转换成Java的Integer对象
*
* @param context context
* @return Java中的Integer对象
*/
@Override
public Integer convertToJavaData(ReadConverterContext<?> context) {
return context.getReadCellData().getStringValue().equals("男") ? 1 : 0;
}
/**
* 这里是写的时候会调用,将Java的Integer对象转换成Excel中的字符串
*
* @return Excel中要存储的字符串
*/
@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<Integer> context) {
String gender = context.getValue() == 1 ? "男" : "女";
return new WriteCellData<String>(gender);
}
}
/**
* 从Excel文件流中分批导入数据到库中
* EasyExcel参考文档:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read
*
* @param <T>
* @author wangrubin
* @date 2022-08-02
*/
@Slf4j
public abstract class ExcelImportListener<T> implements ReadListener<T> {
/**
* 缓存大小
*/
private static final int BATCH_SIZE = 100;
/**
* 缓存数据
*/
private List<T> cacheList = new ArrayList<>(BATCH_SIZE);
@Override
public void invoke(T po, AnalysisContext analysisContext) {
cacheList.add(po);
if (cacheList.size() >= BATCH_SIZE) {
log.info("完成一批Excel记录的导入,条数为:{}", cacheList.size());
getMapper().batchInsert(cacheList);
cacheList = new ArrayList<>(BATCH_SIZE);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
getMapper().batchInsert(cacheList);
log.info("完成最后一批Excel记录的导入,条数为:{}", cacheList.size());
}
/**
* 获取批量插入的Mapper
* @return 批量插入的Mapper
*/
protected abstract BatchInsertMapper<T> getMapper();
}
import com.alibaba.excel.EasyExcel;
import com.spring.accumulator.dao.BatchInsertMapper;
import com.spring.accumulator.dao.PersonMapper;
import com.spring.accumulator.entity.PersonPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
/**
* Excel导入组件
*
* @author wangrubin
* @date 2022-08-02
*/
@Slf4j
@Component
public class ExcelComponent {
@Resource
private PersonMapper personMapper;
/**
* Excel文件分批导入数据库
*
* @param file 上传的文件
* @throws IOException 读取文件异常
*/
public void importPersonFile(MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream())
.head(PersonPO.class)
.registerReadListener(new ExcelImportListener<PersonPO>() {
@Override
protected BatchInsertMapper<PersonPO> getMapper() {
return personMapper;
}
}).sheet().doRead();
}
}
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@RestController
@RequestMapping("/excel")
public class ImportController {
@Resource
private ExcelComponent excelComponent;
@PostMapping("/import-person")
public Boolean importPersonFile(@RequestParam("file") MultipartFile file) throws IOException {
excelComponent.importPersonFile(file);
return true;
}
导出也会用到导入阶段定义的POJO和Converter,此处不再赘述。
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* 将数据以Excel的格式写入输出流
* EasyExcel参考文档:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write
*
* @author wangrubin
* @date 2022-08-02
*/
@Slf4j
@Component
public class ExcelExportHandler {
/**
* 下载Excel格式的数据
*
* @param response response
* @param fileName 文件名(支持中文)
* @param data 待下载的数据
* @param clazz 封装数据的POJO
* @param <T> 数据泛型
*/
public <T> void export(HttpServletResponse response, String fileName,
List<T> data, Class<T> clazz) {
try {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename*=utf-8''" + encodedFileName + ".xlsx");
// 这里需要设置不关闭流
EasyExcel.write(response.getOutputStream(), clazz)
.sheet("Sheet1")
// 设置单元格宽度自适应
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 设置单元格高度和字体
.registerWriteHandler(getHeightAndFontStrategy())
.doWrite(data);
log.info("下载{}条记录到文件{}", data.size(), fileName);
} catch (Exception e) {
// 重置response
log.error("文件下载失败" + e.getMessage());
throw new RuntimeException("下载文件失败", e);
}
}
/**
* 自定义Excel导出策略,设置表头和数据行的字体和高度
*
* @return Excel导出策略
*/
private HorizontalCellStyleStrategy getHeightAndFontStrategy() {
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontHeightInPoints((short) 11);
headWriteFont.setBold(true);
headWriteCellStyle.setWriteFont(headWriteFont);
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
WriteFont contentWriteFont = new WriteFont();
contentWriteFont.setFontHeightInPoints((short) 11);
contentWriteCellStyle.setWriteFont(contentWriteFont);
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
}
}
注意:用postman测试的时候,不要在选择文件存储路径时修改文件名,要不然下载到本地的文件格式会出错。
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.spring.accumulator.dao.PersonMapper;
import com.spring.accumulator.entity.PersonPO;
import com.spring.accumulator.io.excel.ExcelExportHandler;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@RequestMapping("/excel")
public class ImportController {
@Resource
private PersonMapper personMapper;
@Resource
private ExcelExportHandler excelExportHandler;
@GetMapping("/export-person")
public void exportPersonFile(HttpServletResponse response) {
List<PersonPO> data = personMapper.selectList(new QueryWrapper<>());
excelExportHandler.export(response, "人员表", data, PersonPO.class);
}
}
本文的主要目的是,从Controller层到Dao层全流程演示Excel文件的导入导出,代码详尽,复制即可运行。
最后,本文测试的Excel文件内容和数据库中的数据如下:


我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
我想用ruby编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序
好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信
我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A