序文
私たちのプロジェクト要件では、エクスポート要件が頻繁に発生しますが、その中で最も一般的なのは Excel エクスポートです。Excel を生成するためのより有名なフレームワークには、Apache poi、jxl などが含まれますが、いずれも大量のメモリを消費するという深刻な問題があります。システムに大量の同時実行性がない場合は問題ないかもしれませんが、一度は同時実行が発生すると、確実に OOM または JVM が発生します。
1. EasyExcelの特徴
EasyExcel は Alibaba のオープン ソースの Excel 処理フレームワークです。使いやすさとメモリの節約で有名です。64M の
メモリで 1 分間に 75M (46W 行と 25 列) の Excel を読み取ることができます (もちろん、高速な処理もあります)。モードを使用すると高速になりますが、メモリ使用量が増加します (100M を少し超える)。
EasyExcel がメモリ使用量を大幅に削減できる主な理由は、Excel を解析するときにファイル データが一度にメモリに読み込まれるのではなく、データがディスクから 1 行ずつ読み込まれ、1 つずつ解析されるためです。
サポートされていない機能
1. 単一ファイルの同時書き込み
2. 画像の読み込みと読み込み
3. マクロ
4. CSV読み込み(今後検討する可能性あり)
3. よくある質問
1. ファイルの読み取りには必ず 2.0.5 以降を使用してください (現在、プロジェクトでは 2.2.10 が使用されています)
2. リフレクション オブジェクトの読み取りおよび書き込みには Cglib 動的プロキシが使用されるため、メンバー変数はキャメル ケース仕様に準拠する必要があります。 @Data (chain = true) を使用する場合、@Accessors は使用できません。キャメルケース以外の対応については今後検討してまいります。
3. NoSuchMethodException、ClassNotFoundException、NoClassDefFoundError が発生します。高い可能性は、jar の競合です。プロジェクトをクリーンアップするか、POI バージョンを統合することをお勧めします。理論的には、easyexcel は、POI 3.17、4.0.1、4.1.0 のすべての新しいバージョンと互換性があります。4. 数値を受け取るには文字列を使用します
。と小数点が表示されますが、バグですが修正は困難です。この問題は次のバージョンで修正される予定です。現時点では @NumberFormat アノテーションを使用してください。内部のパラメータは Java に付属の NumberFormat.format メソッドを呼び出します。パラメータの入力方法がわからない場合は、オンラインで確認できます。
easyExcelの公式ドキュメントアドレス:https://alibaba-easyexcel.github.io/index.html
4、共通の注釈
4-1. 読む
ExcelProperty は、現在のフィールドが対応する Excel の列を指定します。名前またはインデックスに基づいて照合できます。もちろん、これを記述する必要はありません。デフォルトの最初のフィールドは、index=0 などです。何も書き込まないようにするか、インデックスを使用するか、一致する名前を使用するように注意してください。ソース コード内で 3 つを並べ替える方法をよく知っている場合を除き、これら 3 つを決して混合しないでください。
@Getter
@Setter
@EqualsAndHashCode
public class IndexOrNameData{
//强制读取第三个这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去山配*
@ExcelProperty(index = 2)
private Double doubleData
/*用名字去匹配,这里需要注意,如果名字重复会导致只有一个字殷读取到数据@ExcelProperty ("字符串标题”) */
private Stringstring;
@ExcelProperty ("日期标题")
private Date date;
}
ExcelIgnore のデフォルトでは、Excel に一致するすべてのフィールドが設定されます。この注釈を追加すると、フィールドは無視されます。
//强制读取第三个这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去山配*
@ExcelIgnore
private Double doubleData
DateTimeFormat の日付変換では、文字列を使用して Excel の日付形式でデータを受信すると、この注釈が呼び出されます。内部の値は java.text.SimpleDateFormat を参照します。
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
private String date;
NumberFormat の数値変換では、String を使用して Excel 数値形式でデータを受信すると、この注釈が呼び出されます。内部の値は java.text.DecimalFormat を参照します。
@NumberFormat("#.##%")
private String doubleData; //接收百比的数字
4-2.書き込み
ExcelProperty インデックスは、書き込む列を指定し、デフォルトではメンバー変数によって並べ替えられます。値には、書き込まれる名前、つまりデフォルトのメンバー変数の名前を指定します。複数の値の場合、クイック スタートで複雑なヘッダー ExcelIgnore を参照できます。デフォルトでは、すべての
フィールドが Excel に書き込まれます。この注釈はこのフィールドを無視します。 .
DateTimeFormat 日付変換. Excel に Date を書き込むと、この注釈が呼び出されます。中の値は java.text.SimpleDateFormat
NumberFormat の数値変換を参照しており、Excel に Number を記述するとこのアノテーションが呼び出されます。中の値はjava.text.DecimalFormat
ExcelIgnoreUnannotatedを参照しており、デフォルトではExcelPropertyアノテーションが付加されていない場合は読み書きに参加しますが、付加されている場合は読み書きに参加しません。
5. EasyExcelの使用
1.依存性
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.10</version>
</dependency>
2.エクセルを読む
2.1 最も単純な方法
物体
@Data
public class DemoData {
private String string;
private Date date;
private Double doubleData;
}
コントローラクラス
@PostMapping("/outstoragcExce1")
@Apioperation("读取出库excel表")
public DeviceResponse storageservice(@RequestBody MultipartFile file) {
try{
storageservice.storageservice(file);
} catch (Exception e){
return new DeviceResponse(Constant.FAIL CODE,"出库导失败");
}
return new DeviceResponse(Constant.SUCCESS CODE,"出库导入成功");
}
サービス実装クラス
@Autowired
private StorageService storageServicel
@override
publil void slorageservice(MulliparlFile file) {
Tnnutstream is = null:
try{
is=file.getInputstream();
} catch (IDException e){
e.printstackTrace();
}
//1.进行读取数数据,slorageReLrieval是我的puju类,
//2.new Soragelistenpr(storagpServire)这个是监听器,主要用来i取数据的,别急后面会讲
//3.特别注意的是storageservice这个service,我上面有注入进去 @Autowired,切记不要new会报错
EasyExcel.read(is,StorageRetrieval.class, new Soragelisterer(storageService))sheet().doRead();
}
SorageListener リスナー
@Component
public class SorageListener extends AnalysisEventListener<pojo类> {
private static final Logger LOGGER = LoggerFactory.getLogger(SorageListener.class);
//读取数据初始化值
private static final int BATCH_COUNT = 50;
List<pojo类> list = new ArrayList<pojo类>();
private StorageService storageService;
public SorageListener() {
storageService=new StorageServiceImpl();
}
public SorageListener(StorageService storageService) {
this.storageService = storageService;
}
/**
* 这个每一条数据解析都会来调用,数据是一条一条进行解析的
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(StorageRetrieval data, AnalysisContext context) {
list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}
/**
* 所有excel表中数据解析完成了 都会来调用这个
* 解释为什么要保存数据?
*初始化读取数量为50,表中信息已经加载完毕,,假设excel表中最后只剩下30行遗留数据,所以为了防止存在遗留数据 尽量判断下集合是否为空,不为空在进行存储(这是我的逻辑需要判断,如果不需要也可进行不判断)
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
if(list.size()==0){
return;
}
saveData();
LOGGER.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
public void saveData() {
storageService.save(list); //代码实现类层保存数据
LOGGER.info("存储数据库成功!");
}
}
2.2. カラムの添え字またはカラム名を指定する
@Data
public class IndexOrNameData {
/**
* 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
*/
@ExcelProperty(index = 2)
private Double doubleData;
/**
* 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
*/
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
}
/**
* 指定列的下标或者列名
*
* <p>1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}
* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}
* <p>3. 直接读即可
*/
@Test
public void indexOrNameRead() {
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里默认读取第一个sheet
EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();
}
3.エクセルを書く
3.1 最も単純な方法
エンティティオブジェクト
@Data
@piModel(value = "年龄统计实体类”
public class FToWAgeStatisticalVo implements Serializable {
private static final long serialVersionUID = -7891558029837989473L;
@ApiModelProperty("区间")
@ExcelProperty(value = "区间")
private String ageGap;
@ApiModeLProperty("病例数”)
@ExcelProperty(value ="病例数)
private Integer casesNumber ;
@ApiModeProperty("密接数”)
@ExceProperty(value = "密接数”)
private Integer closeNumber ;
}
サービスの実装
@Override
public void avestatisticalExcel(Httpservlethesponse resonse,FlowReionStatisticalParam flowReionStatisticalParam) throws Exception{
//这里文件名如果涉及中文一定要使用URL编码,否则会乱码
String fileName = URLEncoder.encode( s: "floWAgeStatistical.xlsx" StandardCharsets.UTF_8.toString());
List<FloWAgeStatisticalVo> data = ageStatistical(flowRegionStatisticalParam);
response.setContentType("application/force-download");
response.setcharacterEncoding("utf-8");
response.setHeader( s: "Content-Disposition", s1: "attachment;filename=" + fileName);
EasyExcel.write(response.getoutputstream(),FLoWAgeStatisticalVo.class)
.autoclosestream(true)
.exceType(ExcelTypeEnum.XLSX)
.sheet( sheetName: "年龄统计表")
.doWrite(data) ;
}
3.2. 列の幅と行の高さ
@Data
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
/**
* 宽度为50
*/
@ColumnWidth(50)
@ExcelProperty("数字标题")
private Double doubleData;
}
3.3. セルの結合
@Getter
@Setter
@EqualsAndHashCode
// 将第6-7行的2-3列合并成一个单元格
// @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
public class DemoMergeData {
// 这一列 每隔2行 合并单元格
@ContentLoopMerge(eachRow = 2)
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
}
/**
* 合并单元格
* <p>1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>2. 创建一个merge策略 并注册
* <p>3. 直接写即可
*/
@Test
public void mergeWrite() {
String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";
// 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写
LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板")
.doWrite(data());
}
3.4. 複雑なヘッダーの書き込み
@Data
@ApiModel("学校学生缺勤信息")
public class SchoolAnalyseVo {
@ApiModelProperty("学校Id")
@ExcelIgnore()
private Long schoolId;
@ExcelProperty("学校")
private String schoolName;
@ExcelProperty("学校类型")
private String schoolType;
..........
@ExcelProperty({
"症状", "发热"})
private String fever;
@ExcelProperty({
"症状", "咳嗽"})
private String cough;
@ExcelProperty({
"症状", "头痛"})
private String headache;
.........
@ExcelProperty({
"疾病","普通感冒", "人数"})
private String commonColdNumber ;
@ExcelProperty({
"疾病","普通感冒", "因病缺勤率"})
private String commonColdRate ;
@ExcelProperty({
"疾病","流感", "人数"})
private String influenzaNumber;
.........
}
3.5、日付、数値、またはカスタム形式の変換
@Data
public class ConverterData {
/**
* 我想所有的 字符串起前面加上"自定义:"三个字
*/
@ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)
private String string;
/**
* 我想写到excel 用年月日的格式
*/
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
@ExcelProperty("日期标题")
private Date date;
/**
* 我想写到excel 用百分比表示
*/
@NumberFormat("#.##%")
@ExcelProperty(value = "数字标题")
private Double doubleData;
}
カスタムコンバーター
public class CustomStringStringConverter implements Converter<String> {
@Override
public Class supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 这里读的时候会调用
*
* @param cellData
* @param contentProperty
* @param globalConfiguration
* @return
*/
@Override
public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return "自定义:" + cellData.getStringValue();
}
/**
* 这里是写的时候会调用 不用管
*
* @param value
* @param contentProperty
* @param globalConfiguration
* @return
*/
@Override
public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return new CellData(value);
}
}
3.6. 書き込みカラムの指定
@Getter
@Setter
@EqualsAndHashCode
public class IndexData {
@ExcelProperty(value = "字符串标题", index = 0)
private String string;
@ExcelProperty(value = "日期标题", index = 1)
private Date date;
/**
* 这里设置3 会导致第二列空的
*/
@ExcelProperty(value = "数字标题", index = 3)
private Double doubleData;
}
3.7. その他の読み取り操作
https://www.yuque.com/easyexcel/doc/write
4.エクセルに記入する
4.1 最も単純な充填
物体
@Getter
@Setter
@EqualsAndHashCode
public class FillData{
private string name;
private double number;
private Date date;
}
コード
/*最简单的填充
* @since 2.1.1
*/
@Test
public void simpleFill() [
// 模板注 用]来表示你要用的变量 如果本来就有””,”]”特殊字符 用””]"代替
String templateFileName =TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "simple.xlsx";
// 方案1 根据对象填充
String fileName = TestFileUtil,getPath() + "simpleFill" + System,currentTimeMillis() + ".xlsx",
// 这里 会填充到第一个sheet, 然后文件流会自动关闭
FillData fillData = new FillData();
fillData.setName("张一");
fillData.setNumber(5.2);
EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);
// 方案2 根据Map填充
fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx"
// 这里 会填充到第一个sheet, 然后文件流会自动关闭
Map<string, Object> map = new HashMap<string, Object>();
map.put("name”,"张二");
map .put("number", 5.2) :
EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);
}
4.2. その他の詰め物
リストの充填、複雑な充填、大量のデータによる複雑な充填、水平充填、複数リストの組み合わせの充填
https://www.yuque.com/easyexcel/doc/fill