导出Excel的技术分享-综合篇
导出Excel的技术分享-综合篇
简单的EasyExcel使用
/*** 最简单的写*/public void simpleWrite() {// 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入// 写法1 JDK8+// since: 3.0.0-beta1String fileName = "simpleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 如果这里想使用03 则 传入excelType参数即可EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());}private List<DemoData> data() {List<DemoData> list = ListUtils.newArrayList();for (int i = 0; i < 10; i++) {DemoData data = new DemoData();data.setString("字符串" + i);data.setDate(new Date());data.setDoubleData(0.56);list.add(data);}return list;}
行标题设置
public static void main(String[] args) {String powerTitle = "行单元格样例演示\n" +"行单元格样例演示\n";ExcelWriter excelWriter = EasyExcel.write("行标题设置.xlsx").build();WriteSheet writeSheet = EasyExcel.writerSheet(0,"行标题设置").head(DemoData.class).relativeHeadRowIndex(2).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).registerWriteHandler(new IndexStandardStyleHandler(powerTitle)).build();excelWriter.write(data(),writeSheet);excelWriter.finish();}
public class IndexStandardStyleHandler implements SheetWriteHandler {private String tableTitle;public IndexStandardStyleHandler(String title) {this.tableTitle = title;}@Overridepublic void afterSheetCreate(SheetWriteHandlerContext context) {// 在创建工作表之后执行的操作WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
// ExcelWriteHeadProperty excelWriteHeadProperty = writeSheetHolder.getExcelWriteHeadProperty();
// excelWriteHeadProperty.setHeadRowNumber(3);// 设置行高和样式// 创建第一行并设置样式Row row = writeSheetHolder.getSheet().createRow(0);row.setHeight((short) 1000);Cell cell = row.createCell(0);cell.setCellValue(tableTitle);//设置标题// 准备样式CellStyle cellStyle = context.getWriteWorkbookHolder().getWorkbook().createCellStyle();cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);cellStyle.setAlignment(HorizontalAlignment.CENTER);Font font = context.getWriteWorkbookHolder().getWorkbook().createFont();
// font.setBold(true);font.setFontHeight((short) 500);font.setFontName("宋体");font.setColor(HSSFColor.HSSFColorPredefined.RED.getIndex());cellStyle.setFont(font);cellStyle.setAlignment(HorizontalAlignment.CENTER);cellStyle.setWrapText(true);cell.setCellStyle(cellStyle);//writeWorkbookHolder.createCellStyle(style,null);Sheet sheet = context.getWriteSheetHolder().getSheet();sheet.addMergedRegionUnsafe(new CellRangeAddress(0, 1, 0, 2));}@Overridepublic int order() {return 3;}
}
单元格线设置
public static void main(String[] args) {String powerTitle = "行单元格样例演示\n" +"行单元格样例演示\n";ExcelWriter excelWriter = EasyExcel.write("单元格线设置.xlsx").build();WriteSheet writeSheet = EasyExcel.writerSheet(0,"单元格线设置").head(DemoData.class).relativeHeadRowIndex(2).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).registerWriteHandler(new CelllineStyleHandler()).registerWriteHandler(new IndexStandardStyleHandler(powerTitle)).build();excelWriter.write(data(),writeSheet);excelWriter.finish();}
public class CelllineStyleHandler implements CellWriteHandler {@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {for (int i = 0; i < cellDataList.size(); i++) {WriteCellData<?> writeCellData = cellDataList.get(i);WriteCellStyle writeCellStyle = writeCellData.getWriteCellStyle();writeCellStyle.setBorderLeft(THIN);writeCellStyle.setBorderRight(THIN);writeCellStyle.setBorderTop(THIN);writeCellStyle.setBorderBottom(THIN);writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);writeCellStyle.setShrinkToFit(true);}}@Overridepublic int order() {return 1;}
}
下拉框设置
public static void main(String[] args) {String powerTitle = "行单元格样例演示\n" +"行单元格样例演示\n";ExcelWriter excelWriter = EasyExcel.write("下拉框设置.xlsx").build();WriteSheet writeSheet = EasyExcel.writerSheet(0,"下拉框设置").head(DemoData.class).relativeHeadRowIndex(2).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).registerWriteHandler(new CelllineStyleHandler()).registerWriteHandler(new CellDropDownBoxWriteHandler()).registerWriteHandler(new IndexStandardStyleHandler(powerTitle)).build();excelWriter.write(data(),writeSheet);excelWriter.finish();}
public class CellDropDownBoxWriteHandler implements SheetWriteHandler {/*** 定义一个map key是需要添加下拉框的列的index value是下拉框数据*/Map<Integer, String[]> mapDropDown = new HashMap<>();String[] checkFrequency = {"字符串0","字符串1","字符串2","字符串3","字符串4"};@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {//下拉选在Excel中对应的列Field[] fields = writeSheetHolder.getClazz().getDeclaredFields();for (int i = 0; i < fields.length; i++) {Field field = fields[i];if (field.isAnnotationPresent(ExcelProperty.class) && field.isAnnotationPresent(ExcelProperty.class)){mapDropDown.put(i,checkFrequency);}}//获取工作簿Sheet sheet = writeSheetHolder.getSheet();///开始设置下拉框DataValidationHelper helper = sheet.getDataValidationHelper();//设置下拉框for (Map.Entry<Integer, String[]> entry : mapDropDown.entrySet()) {/*起始行、终止行、起始列、终止列 起始行为1即表示表头不设置**///这里设置65535可能又问题,因为这个是excel的最大行数,如果数据量超过这个数,就会报错CellRangeAddressList addressList = new CellRangeAddressList(1, 65535, entry.getKey(), entry.getKey());/*设置下拉框数据**/DataValidationConstraint constraint = helper.createExplicitListConstraint(entry.getValue());DataValidation dataValidation = helper.createValidation(constraint, addressList);//阻止输入非下拉选项的值dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);dataValidation.setShowErrorBox(true);dataValidation.setSuppressDropDownArrow(true);dataValidation.createErrorBox("提示", "输入值与单元格定义格式不一致");dataValidation.createPromptBox("填写说明", "填写内容只能为下拉数据集中的类型");sheet.addValidationData(dataValidation);}}@Overridepublic int order() {return 1;}
}
标题和内容字体样式设置不一样
public static void main(String[] args) {String powerTitle = "行单元格样例演示\n" +"行单元格样例演示\n";ExcelWriter excelWriter = EasyExcel.write("标题和内容字体样式不一样设置.xlsx").build();WriteSheet writeSheet = EasyExcel.writerSheet(0,"标题和内容字体样式不一样设置").head(DemoData.class).relativeHeadRowIndex(2).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).registerWriteHandler(new CelllineStyleHandler()).registerWriteHandler(new CellDropDownBoxWriteHandler()).registerWriteHandler(new FontStyleWriteHandler()).registerWriteHandler(new IndexStandardStyleHandler(powerTitle)).build();excelWriter.write(data(),writeSheet);excelWriter.finish();}
public class FontStyleWriteHandler implements CellWriteHandler {/*** 设置字体样式* @param writeSheetHolder* @param writeTableHolder Nullable.It is null without using table writes.* @param cellDataList Nullable.It is null in the case of add header.There may be several when fill the data.* @param cell* @param head Nullable.It is null in the case of fill data and without head.* @param relativeRowIndex Nullable.It is null in the case of fill data.* @param isHead It will always be false when fill data.*/@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {for (int i = 0; i < cellDataList.size(); i++) {WriteCellData<?> writeCellData = cellDataList.get(i);WriteCellStyle writeCellStyle = writeCellData.getWriteCellStyle();if (2 == cell.getRowIndex()) {WriteFont writeFont = new WriteFont();writeFont.setBold(true);writeFont.setFontHeightInPoints((short) 13);writeFont.setFontName("楷体");writeCellStyle.setWriteFont(writeFont);} else {WriteFont writeFont = new WriteFont();writeFont.setFontName("宋体");writeCellStyle.setBorderLeft(THIN);writeCellStyle.setBorderRight(THIN);writeCellStyle.setBorderTop(THIN);writeCellStyle.setBorderBottom(THIN);writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);writeCellStyle.setWriteFont(writeFont);
// writeCellStyle.setShrinkToFit(true);}}}@Overridepublic int order() {return 1;}
}
单元格合并设置
public static void main(String[] args) {String powerTitle = "行单元格样例演示\n" +"行单元格样例演示\n";ExcelWriter excelWriter = EasyExcel.write("单元格合并设置.xlsx").build();WriteSheet writeSheet = EasyExcel.writerSheet(0,"单元格合并设置").head(DemoData.class).relativeHeadRowIndex(2).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).registerWriteHandler(new CelllineStyleHandler()).registerWriteHandler(new CellDropDownBoxWriteHandler()).registerWriteHandler(new FontStyleWriteHandler()).registerWriteHandler(new ExcelFillCellMergeStrategyHandler(2,new int[]{0,1,2})).registerWriteHandler(new IndexStandardStyleHandler(powerTitle)).build();excelWriter.write(data(),writeSheet);excelWriter.finish();}
public class ExcelFillCellMergeStrategyHandler implements CellWriteHandler {/*** 合并字段的下标,如第一到五列new int[]{0,1,2,3,4}*/private int[] mergeColumnIndex;/*** 从第几行开始合并,如果表头占两行,这个数字就是2*/private int mergeRowIndex;public ExcelFillCellMergeStrategyHandler(int mergeRowIndex, int[] mergeColumnIndex) {this.mergeRowIndex = mergeRowIndex;this.mergeColumnIndex = mergeColumnIndex;}@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,List<WriteCellData<?>> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {//当前行int curRowIndex = cell.getRowIndex();//当前列int curColIndex = cell.getColumnIndex();if (curRowIndex > mergeRowIndex) {for (int i = 0; i < mergeColumnIndex.length; i++) {if (curColIndex == mergeColumnIndex[i]) {mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);break;}}}for (int i = 0; i < list.size(); i++) {WriteCellData<?> writeCellData = list.get(i);WriteCellStyle writeCellStyle = writeCellData.getWriteCellStyle();writeCellStyle.setBorderLeft(THIN);writeCellStyle.setBorderRight(THIN);writeCellStyle.setBorderTop(THIN);writeCellStyle.setBorderBottom(THIN);writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);writeCellStyle.setShrinkToFit(true);}}private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {//获取当前行的当前列的数据和上一行的当前列列数据,通过上一行数据是否相同进行合并Object curData = cell.getCellType() == CellType.STRING ? cell.getStringCellValue() :cell.getNumericCellValue();Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);Object preData = preCell.getCellType() == CellType.STRING ? preCell.getStringCellValue() :preCell.getNumericCellValue();// 比较当前行的第一列的单元格与上一行是否相同,相同合并当前单元格与上一行//if (curData.equals(preData)) {Sheet sheet = writeSheetHolder.getSheet();List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();boolean isMerged = false;for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {CellRangeAddress cellRangeAddr = mergeRegions.get(i);// 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {sheet.removeMergedRegion(i);cellRangeAddr.setLastRow(curRowIndex);sheet.addMergedRegion(cellRangeAddr);isMerged = true;}}// 若上一个单元格未被合并,则新增合并单元if (!isMerged) {CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex,curColIndex);sheet.addMergedRegion(cellRangeAddress);}}}@Overridepublic int order() {return 1;}
}
自定义多个sheet页导出设置
public class Test07 {public static void main(String[] args) throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException {String powerTitle = "行单元格样例演示\n" +"行单元格样例演示\n";ExcelWriter excelWriter = EasyExcel.write("自定义多个sheet页导出设置.xlsx").build();// 第一个sheet的写入WriteSheet writeSheet01 = EasyExcel.writerSheet(0,"自定义多个sheet页导出设置").head(DemoData.class).relativeHeadRowIndex(2).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).registerWriteHandler(new CelllineStyleHandler()).registerWriteHandler(new CellDropDownBoxWriteHandler()).registerWriteHandler(new FontStyleWriteHandler()).registerWriteHandler(new ExcelFillCellMergeStrategyHandler(2,new int[]{0,1,2})).registerWriteHandler(new IndexStandardStyleHandler(powerTitle)).build();excelWriter.write(data(),writeSheet01);// 后续sheet写入Map map = listGroupInfo(DemoData.class, dataList());int i = 1;Map<String,String> orgNameMap = new HashMap<>();orgNameMap.put("自定义sheet页1","自定义sheet页1");orgNameMap.put("自定义sheet页2","自定义sheet页2");orgNameMap.put("自定义sheet页3","自定义sheet页3");orgNameMap.put("自定义sheet页4","自定义sheet页4");for (Map.Entry<String, String> entry : orgNameMap.entrySet()) {String OrgCode = entry.getKey();String orgName = entry.getValue();Object list = map.get(OrgCode);// 设置非供电单位写入标题String noPowerTitle = "行单元格样例演示\n" +OrgCode+"行单元格样例演示\n";;WriteSheet sheet = EasyExcel.writerSheet(i, orgName).head(DemoData.class).relativeHeadRowIndex(2).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).build();excelWriter.write((Collection<?>) list,sheet);i = i + 1;}excelWriter.finish();}private static Map listGroupInfo(Class<?> head1, List<?> dataList1) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {String groupField = "string";String name = head1.getName();Class<?> clazz = Class.forName(name);Field field = clazz.getDeclaredField(groupField);field.setAccessible(true);Map<Object, ? extends List<?>> map = dataList1.stream().collect(Collectors.groupingBy(obj -> {try {return field.get(obj);} catch (IllegalAccessException e) {e.printStackTrace();return null;}}));Set<Object> objects = map.keySet();Iterator<Object> iterator = objects.iterator();
// while (iterator.hasNext()) {
// List<?> list = map.get(iterator.next());
// for (int i = 0; i < list.size(); i++) {
// Object o = list.get(i);
// Field fieldSerialNumber = clazz.getDeclaredField("serialNumber");
// fieldSerialNumber.setAccessible(true);
// fieldSerialNumber.set(o,String.valueOf(i+1));
// }
// }return map;}private static List<DemoData> data() {List<DemoData> list = ListUtils.newArrayList();for (int i = 0; i < 10; i++) {DemoData data = new DemoData();data.setString("字符串" + i);data.setDate(new Date());data.setDoubleData(0.56);list.add(data);}return list;}private static List<DemoData> dataList() {List<DemoData> list = ListUtils.newArrayList();for (int i = 0; i < 10; i++) {DemoData data1 = new DemoData();data1.setString("自定义sheet页1");data1.setDate(new Date());data1.setDoubleData(111.111);list.add(data1);}for (int i = 0; i < 10; i++) {DemoData data2 = new DemoData();data2.setString("自定义sheet页2");data2.setDate(new Date());data2.setDoubleData(222.222);list.add(data2);}for (int i = 0; i < 10; i++) {DemoData data3 = new DemoData();data3.setString("自定义sheet页3");data3.setDate(new Date());data3.setDoubleData(333.333);list.add(data3);}for (int i = 0; i < 10; i++) {DemoData data3 = new DemoData();data3.setString("自定义sheet页4");data3.setDate(new Date());data3.setDoubleData(444.444);list.add(data3);}return list;}}
public class ExcelFillCellMergeStrategyHandler implements CellWriteHandler {/*** 合并字段的下标,如第一到五列new int[]{0,1,2,3,4}*/private int[] mergeColumnIndex;/*** 从第几行开始合并,如果表头占两行,这个数字就是2*/private int mergeRowIndex;public ExcelFillCellMergeStrategyHandler(int mergeRowIndex, int[] mergeColumnIndex) {this.mergeRowIndex = mergeRowIndex;this.mergeColumnIndex = mergeColumnIndex;}@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,List<WriteCellData<?>> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {//当前行int curRowIndex = cell.getRowIndex();//当前列int curColIndex = cell.getColumnIndex();if (curRowIndex > mergeRowIndex) {for (int i = 0; i < mergeColumnIndex.length; i++) {if (curColIndex == mergeColumnIndex[i]) {mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);break;}}}for (int i = 0; i < list.size(); i++) {WriteCellData<?> writeCellData = list.get(i);WriteCellStyle writeCellStyle = writeCellData.getWriteCellStyle();writeCellStyle.setBorderLeft(THIN);writeCellStyle.setBorderRight(THIN);writeCellStyle.setBorderTop(THIN);writeCellStyle.setBorderBottom(THIN);writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);writeCellStyle.setShrinkToFit(true);}}private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {//获取当前行的当前列的数据和上一行的当前列列数据,通过上一行数据是否相同进行合并Object curData = cell.getCellType() == CellType.STRING ? cell.getStringCellValue() :cell.getNumericCellValue();Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);Object preData = preCell.getCellType() == CellType.STRING ? preCell.getStringCellValue() :preCell.getNumericCellValue();// 比较当前行的第一列的单元格与上一行是否相同,相同合并当前单元格与上一行//if (curData.equals(preData)) {Sheet sheet = writeSheetHolder.getSheet();List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();boolean isMerged = false;for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {CellRangeAddress cellRangeAddr = mergeRegions.get(i);// 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {sheet.removeMergedRegion(i);cellRangeAddr.setLastRow(curRowIndex);sheet.addMergedRegion(cellRangeAddr);isMerged = true;}}// 若上一个单元格未被合并,则新增合并单元if (!isMerged) {CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex,curColIndex);sheet.addMergedRegion(cellRangeAddress);}}}@Overridepublic int order() {return 1;}
}
单元格锁设置
public static void main(String[] args) {String powerTitle = "行单元格样例演示\n" +"行单元格样例演示\n";ExcelWriter excelWriter = EasyExcel.write("单元格锁设置.xlsx").build();WriteSheet writeSheet = EasyExcel.writerSheet(0,"单元格锁设置").head(DemoDataLock.class).relativeHeadRowIndex(2).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).registerWriteHandler(new CelllineStyleHandler()).registerWriteHandler(new LockHandler()).registerWriteHandler(new IndexStandardStyleHandler(powerTitle)).build();excelWriter.write(data(),writeSheet);excelWriter.finish();}
public class LockHandler extends LongestMatchColumnWidthStyleStrategy implements SheetWriteHandler {@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {// 锁定有内容过的单元格(方法1)Class clazz = context.getWriteSheetHolder().getClazz();Field[] fields = clazz.getDeclaredFields();Field field = fields[context.getColumnIndex()];if (field.isAnnotationPresent(UnLockCell.class) && field.isAnnotationPresent(ExcelProperty.class)) {WriteCellStyle writeCellStyle = context.getFirstCellData().getOrCreateStyle();writeCellStyle.setLocked(false);}}@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {// 设置保护密码writeSheetHolder.getSheet().protectSheet("ZhiChengKeJi");// 锁定单元格不可选中(防止别人直接复制内容到其他excel修改)((SXSSFSheet) writeSheetHolder.getSheet()).lockSelectLockedCells(false);
// ((SXSSFSheet) writeSheetHolder.getSheet()).lockSelectLockedCells(true);}
}
设置大标题行样式
@PostMapping("/exportExcel")public void exportExcel(HttpServletResponse response) throws IOException {// 数据库获取数据List<Profile> list = iProfileService.exportExcel(response);List<ProfileExcel> profileExcels = ObjectConvertUtil.convertList(list, ProfileExcel.class);
// List<ProfileExcel> profileExcels = ExcelConvert.INSTANCE.converList(list);// 输出ExcelUtils.write(response, "大佬信息", "岗位列表", ProfileExcel.class, profileExcels);}
展示工具类中的代码
/*** 将列表以Excel的形式响应给前端** @param response 响应* @param fileName 文件名* @param sheetName Excel sheet 名* @param head Excel head 头* @param data 数据列表* @param <T> 泛型,保证 head 和 data 类型的一致性* @throws IOException 写入失败的情况*/public static <T> void write(HttpServletResponse response, String fileName, String sheetName, Class<T> head, List<T> data) throws IOException {// 这一部分是设置编码样式response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");// 输出ExcelEasyExcel.write(response.getOutputStream(),head).relativeHeadRowIndex(2)// 不要自动关闭,交给 Servlet 自己处理//.autoCloseStream(false).registerWriteHandler(new ExportIndexTypeSheetWriteHandler()).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).registerWriteHandler(new ExcelFillCellMergeStrategy(3,new int[]{0,1,2,3,4,5,6})).registerWriteHandler(new FontStyleWriteHandler()).sheet(sheetName).doWrite(data);}
核心注册类代码展示
实现SheetWriteHandler策略接口,在sheet创建之前完成对样式进行提前的设置
public class ExportIndexTypeSheetWriteHandler implements SheetWriteHandler {@Overridepublic void afterSheetCreate(SheetWriteHandlerContext context) {// 在创建工作表之后执行的操作WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
// ExcelWriteHeadProperty excelWriteHeadProperty = writeSheetHolder.getExcelWriteHeadProperty();
// excelWriteHeadProperty.setHeadRowNumber(3);// 设置行高和样式// 创建第一行并设置样式Row row = writeSheetHolder.getSheet().createRow(0);// 设置第一行的高为row.setHeight((short) 1000);// 提前创建第一行单元格并进行设置Cell cell = row.createCell(0);// 设置需要展示的文字信息String title = "大佬们的信息展示\n" +"这个是演示的数据标题\r\n";// 单元格填充内容cell.setCellValue(title);//设置标题// 准备样式// 从上下文内容中获取工作簿并创建样式CellStyle cellStyle = context.getWriteWorkbookHolder().getWorkbook().createCellStyle();// 这个接口在ss。POI的核心模块中存在// 设置单元格的垂直对齐类型 : 垂直对齐以单元格高度为中心。cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 设置单元格的水平对齐样式:水平对齐居中,这意味着文本在单元格中居中。cellStyle.setAlignment(HorizontalAlignment.CENTER);// 从上下文获取字体Font font = context.getWriteWorkbookHolder().getWorkbook().createFont();// 设置字体加粗font.setBold(true);// 设置字体行高font.setFontHeight((short) 500);// 设置字体的样式名称font.setFontName("宋体");// 设置字体的颜色font.setColor(HSSFColor.HSSFColorPredefined.RED.getIndex());// 需要在单元格样式中填充字体cellStyle.setFont(font);// 单元格设置为水平居中cellStyle.setAlignment(HorizontalAlignment.CENTER);// 设置单元格为允许换行,不然上述title中的单元格换行无效cellStyle.setWrapText(true);// 最后需要将单元格的样式填充到单元格中cell.setCellStyle(cellStyle);//writeWorkbookHolder.createCellStyle(style,null);// 有点重复操作,从上下文中获取默认第一个sheet页的样式Sheet sheet = context.getWriteSheetHolder().getSheet();// 设置单元格的合并区域,给坐标的方式。哪行开始哪行结束。哪列开始那列结束sheet.addMergedRegionUnsafe(new CellRangeAddress(0, 1, 0, 5));}
}
标题行的样式填充颜色改变
颜色9是透明色。
相同单元格的样式合并
这种编码方式需要在代码使用的时候将那几列放到方法中。
public class ExcelFillCellMergeStrategy implements CellWriteHandler {/*** 合并字段的下标,如第一到五列new int[]{0,1,2,3,4}*/private int[] mergeColumnIndex;/*** 从第几行开始合并,如果表头占两行,这个数字就是2*/private int mergeRowIndex;public ExcelFillCellMergeStrategy() {}public ExcelFillCellMergeStrategy(int mergeRowIndex, int[] mergeColumnIndex) {this.mergeRowIndex = mergeRowIndex;this.mergeColumnIndex = mergeColumnIndex;}@Overridepublic void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,Head head, Integer integer, Integer integer1, Boolean aBoolean) {}@Overridepublic void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell,Head head, Integer integer, Boolean aBoolean) {}@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,List<WriteCellData<?>> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {//当前行int curRowIndex = cell.getRowIndex();//当前列int curColIndex = cell.getColumnIndex();if (curRowIndex > mergeRowIndex) {for (int i = 0; i < mergeColumnIndex.length; i++) {if (curColIndex == mergeColumnIndex[i]) {mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);break;}}}}private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {//获取当前行的当前列的数据和上一行的当前列列数据,通过上一行数据是否相同进行合并Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() :cell.getNumericCellValue();Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() :preCell.getNumericCellValue();// 比较当前行的第一列的单元格与上一行是否相同,相同合并当前单元格与上一行//if (curData.equals(preData)) {Sheet sheet = writeSheetHolder.getSheet();List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();boolean isMerged = false;for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {CellRangeAddress cellRangeAddr = mergeRegions.get(i);// 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {sheet.removeMergedRegion(i);cellRangeAddr.setLastRow(curRowIndex);sheet.addMergedRegion(cellRangeAddr);isMerged = true;}}// 若上一个单元格未被合并,则新增合并单元if (!isMerged) {CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex,curColIndex);sheet.addMergedRegion(cellRangeAddress);}}}}
单元格的横线设置
public class FontStyleWriteHandler implements CellWriteHandler {/*** 设置字体样式* @param writeSheetHolder* @param writeTableHolder Nullable.It is null without using table writes.* @param cellDataList Nullable.It is null in the case of add header.There may be several when fill the data.* @param cell* @param head Nullable.It is null in the case of fill data and without head.* @param relativeRowIndex Nullable.It is null in the case of fill data.* @param isHead It will always be false when fill data.*/@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {// 创建字体样式// 设置内容字体样式WriteFont writeFont = new WriteFont();writeFont.setFontName("宋体");for (int i = 0; i < cellDataList.size(); i++) {WriteCellData<?> writeCellData = cellDataList.get(i);WriteCellStyle writeCellStyle = writeCellData.getWriteCellStyle();// 横线的设置writeCellStyle.setBorderLeft(THIN);writeCellStyle.setBorderRight(THIN);writeCellStyle.setBorderTop(THIN);writeCellStyle.setBorderBottom(THIN);writeCellStyle.setWriteFont(writeFont);writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);writeCellStyle.setShrinkToFit(true);}}}
导出字体的样式自定义
public class FontStyleWriteHandler implements CellWriteHandler {/*** 设置字体样式* @param writeSheetHolder* @param writeTableHolder Nullable.It is null without using table writes.* @param cellDataList Nullable.It is null in the case of add header.There may be several when fill the data.* @param cell* @param head Nullable.It is null in the case of fill data and without head.* @param relativeRowIndex Nullable.It is null in the case of fill data.* @param isHead It will always be false when fill data.*/@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {// 创建字体样式// 设置内容字体样式WriteFont writeFont = new WriteFont();writeFont.setFontName("宋体");// 核心设置字体样式writeFont.setColor(IndexedColors.RED.getIndex());for (int i = 0; i < cellDataList.size(); i++) {WriteCellData<?> writeCellData = cellDataList.get(i);WriteCellStyle writeCellStyle = writeCellData.getWriteCellStyle();writeCellStyle.setBorderLeft(THIN);writeCellStyle.setBorderRight(THIN);writeCellStyle.setBorderTop(THIN);writeCellStyle.setBorderBottom(THIN);writeCellStyle.setWriteFont(writeFont);writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);writeCellStyle.setShrinkToFit(true);}}}
多级标题实现
相关文章:
![](https://img-blog.csdnimg.cn/d797302e7cdd4e239678ff0d063a1d2d.png)
导出Excel的技术分享-综合篇
导出Excel的技术分享-综合篇 简单的EasyExcel使用 /*** 最简单的写*/public void simpleWrite() {// 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入// 写法1 JDK8// s…...
![](https://img-blog.csdnimg.cn/img_convert/8dd9f3ad494b4d309cfa04903b9c0aa0.jpeg)
iPhone 14四款机型电池容量详细参数揭秘
苹果推出的iPhone 14系列与2021系列的设计和外形尺寸相同(仅缩小了几分之一毫米),所以这并不奇怪,但电池容量也大致相同。 虽然可能不足以对电池寿命产生可衡量的影响,但也存在微小的差异。不同的是,现在有…...
![](https://www.ngui.cc/images/no-images.jpg)
Python功能强大、灵活可扩展的Statsmodels库
Statsmodels是一个功能强大、灵活可扩展的Python库,用于进行统计建模和数据分析。它提供了一系列丰富的统计模型和方法,可以帮助研究人员和数据科学家在Python环境中进行高级统计分析。 概述 在Statsmodels中,线性回归是最常用的统计模型之…...
![](https://img-blog.csdnimg.cn/4bec872d690341ca8ceacd425411aa36.jpeg#pic_center)
AcWing 4405. 统计子矩阵(每日一题)
如果你觉得这篇题解对你有用,可以点点关注再走呗~ 题目描述 给定一个 NM 的矩阵 A,请你统计有多少个子矩阵 (最小 11,最大 NM) 满足子矩阵中所有数的和不超过给定的整数 K ? 输入格式 第一行包含三个整数 N,M 和 K。 之后 N 行每行包含 …...
![](https://img-blog.csdnimg.cn/5f6b1bf7f50841308694954d020ae866.jpeg)
Kali Linux渗透测试技术介绍【文末送书】
文章目录 写在前面一、什么是Kali Linux二、渗透测试基础概述和方法论三、好书推荐1. 书籍简介2. 读者对象3. 随书资源 写作末尾 写在前面 对于企业网络安全建设工作的质量保障,业界普遍遵循PDCA(计划(Plan)、实施(Do…...
![](https://img-blog.csdnimg.cn/2c9934576acf4e71aa516ee2a3b09c30.png)
GPT与BERT模型
NLP任务的核心逻辑是“猜概率”的游戏。BERT和GPT都是基于预训练语言模型的思想,通过大量语料训练得到语言模型。两种模型都是基于Transformer模型。 Bert 类似于Transformer的Encoder部分,GPT类似于Transformer的Decoder部分。两者最明显的在结构上的差…...
![](https://www.ngui.cc/images/no-images.jpg)
2023-09-06力扣每日一题-摆烂暴力
链接: [1123. 最深叶节点的最近公共祖先](https://leetcode.cn/problems/form-smallest-number-from-two-digit-arrays/) 题意: 如题 解: 今天搞一手暴力,按层存,按层取,直到只取到一个 实际代码&…...
![](https://img-blog.csdnimg.cn/img_convert/a531cbfef4d3971d99d0112fac114b18.png)
【Flutter】Flutter 使用 timego 将日期转换为时间描述
【Flutter】Flutter 使用 timego 将日期转换为时间描述 文章目录 一、前言二、安装与基本使用三、如何添加新的语言四、如何覆盖现有的语言或添加自定义消息五、完整示例六、总结 一、前言 你好!我是小雨青年,今天我要为你介绍一个非常实用的 Flutter 包…...
![](https://img-blog.csdnimg.cn/3f53183e3aa449359ceb3fd91084b543.png)
并发容器11
一 JDK 提供的并发容器总结 JDK 提供的这些容器大部分在 java.util.concurrent 包中。 ConcurrentHashMap: 线程安全的 HashMap CopyOnWriteArrayList: 线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector. ConcurrentLinkedQueue: 高效的并…...
![](https://img-blog.csdnimg.cn/2441345ccfed4f22be67e074853bf2e1.png)
Java8实战-总结22
Java8实战-总结22 使用流数值流原始类型流特化数值范围数值流应用:勾股数 使用流 数值流 可以使用reduce方法计算流中元素的总和。例如,可以像下面这样计算菜单的热量: int calories menu.stream().map(Dish::getcalories).reduce(0, Int…...
![](https://www.ngui.cc/images/no-images.jpg)
matlab 实现点云ICP 配准算法
一、算法步骤 (1)在目标点云P中取点集pi∈P; (2)找出源点云Q中的对应点集qi∈Q,使得||qi-pi||=min; (3)计算旋转矩阵R和平移矩阵t,使得误差函数最小; (4)对pi使用上一步求得的旋转矩阵R和平移矩阵t进行旋转和平移变换,的到新的对应点集pi’={pi’=Rpi+t,pi∈P};…...
![](https://www.ngui.cc/images/no-images.jpg)
python提取word文本和word图片
提取文本 docx只支持docx格式,所以如果想读取doc需要另存为docx格式即可 import docx # pip3 install python-docx doc docx.Document(three.docx) for paragraph in doc.paragraphs:print(paragraph.text)提取图片 import zipfile import os, re # docx本质上…...
![](https://img-blog.csdnimg.cn/img_convert/49c14f21c1d00aaf8bc5ef45fb19f68f.png)
iOS开发Swift-9-SFSymbols,页面跳转,view屏幕比例,启动页-和风天气AppUI
1.创建项目 2.设置好测试机型,App显示名称,以及关闭横向展示. 3.下载SF Symbols. https://developer.apple.com/sf-symbols/ 右上角搜索 search ,可以找到很多系统自带图标.选择喜欢的图标,拷贝图标的名字. 插入一个Button,在Image中粘贴图标名称并选择,即可将Button变成想要的…...
![](https://img-blog.csdnimg.cn/f7e6ecdf6f3142cda590f7449ed77cc1.png)
代码优化工具-测试程序执行时间-IDEAdebug+StopWatch
参考: [技巧]IDEA的debugStopWatch监测程序运行时间 添加链接描述 1创建类StopWatchExpand import lombok.extern.slf4j.Slf4j;import org.springframework.util.StopWatch;import java.text.NumberFormat;/*** 检测程序片段运行时间拓展** author sdevil507* cr…...
![](https://img-blog.csdnimg.cn/240df8d1741e4d4f8e17e0ac0a0e1759.png)
力扣每日一题---2594. 修车的最少时间
文章目录 思路解题方法复杂度Code 思路 请注意,能力值越低,修车越快,应该翻译成「排名」,排名越靠前,修车越快。)根据题意可以知道r * n * n < t 的,所以可以利用数学知识进行改变公式&#…...
![](https://img-blog.csdnimg.cn/dd4a5ad7583a4a379d05f0c96c6bee2c.png)
【jvm】运行时数据区
目录 一、运行时数据区一、作用二、说明三、线程共用与私有区域 一、运行时数据区 一、作用 1.内存是非常重要的系统资源,是硬盘和CPU 的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策…...
![](https://img-blog.csdnimg.cn/71badcdb71d94dbd90fca4da2c122cc0.png)
SpringMVC相对路径和绝对路径
1.相对地址与绝对地址定义 在jsp,html中使用的地址,都是在前端页面中的地址,都是相对地址 地址分类:(1),绝对地址,带有协议名称的是绝对地址,http://www.baidu.com&…...
![](https://img-blog.csdnimg.cn/519d3d533d2b4690aeed81f1dc051bca.png)
IIS perl python cbrother php脚本语言配置及简单测试样例程序
上篇笔记写了 IIS 配置 CGI, IIS CGI配置和CGI程序FreeBasic, VB6, VC 简单样例_Mongnewer的博客-CSDN博客 这篇在IIS上配置一些脚本语言。为了操作方便,每种语言在站点下分设文件夹。 1. IIS perl配置 Perl CGI方式是曾经流行的做法。先下载一个开源…...
![](https://www.ngui.cc/images/no-images.jpg)
Oracle Scheduler中日期表达式和PLSQL表达式的区别
参考文档: Database Administrator’s Guide 29.4.5.4 Differences Between PL/SQL Expression and Calendaring Syntax Behavior There are important differences in behavior between a calendaring expression and PL/SQL repeat interval. These differenc…...
![](https://www.ngui.cc/images/no-images.jpg)
Java设计模式:一、六大设计原则-06:依赖倒置原则
文章目录 一、定义:依赖倒置原则二、模拟场景:依赖倒置原则三、违背方案:依赖倒置原则3.1 工程结构3.2 抽奖系统**3.2.1 定义抽奖用户类**3.2.2 抽奖控制 3.3 单元测试 四、改善代码:依赖倒置原则4.1 工程结构4.2 抽奖控制改善4.2…...
![](https://www.ngui.cc/images/no-images.jpg)
信息系统数据同步解决方案
实施数据同步解决方案时,重要的是确保数据同步是安全的、可靠的,并且能够适应系统变化。定期测试和监控数据同步过程,以确保其稳定运行,并随着需求的变化进行适当的调整和优化。 应用场景:信息系统A和信息系统B实现员…...
![](https://img-blog.csdnimg.cn/093c6e7b8ceb4214827fff55250fb84d.png)
LRU算法 vs Redis近似LRU算法
LRU(Least Recently Use)算法,是用来判断一批数据中,最近最少使用算法。它底层数据结构由Hash和链表结合实现,使用Hash是为了保障查询效率为O(1),使用链表保障删除元素效率为O(1)。 LRU算法是用来判断最近最少使用到元素…...
![](https://img-blog.csdnimg.cn/81bd38e9725c42c5bd8a515289f9c511.png)
浅析ARMv8体系结构:异常处理机制
文章目录 概述异常类型中断终止Abort复位Reset系统调用 异常处理流程异常入口异常返回异常返回地址 堆栈选择 异常向量表异常向量表的配置 同步异常解析相关参考 概述 异常处理指的是处理器在运行过程中发生了外部事件,导致处理器需要中断当前执行流程转而去处理异…...
![](https://www.ngui.cc/images/no-images.jpg)
Golang开发--Goroutine的使用
Go 语言天生支持并发编程,提供了丰富的原语和工具来编写并发程序。Goroutine 是 Go 语言中的轻量级执行单位。它们是由 Go 运行时(runtime)管理的,并且能够在单个线程上运行成千上万个 Goroutine。创建 Goroutine 非常高效&#x…...
![](https://www.ngui.cc/images/no-images.jpg)
【Linux】package ‘python-yaml‘ has no installation candidate 如何解决
要解决此问题,可以尝试以下几个步骤: 确保系统已经更新到最新版本。可以使用以下命令进行系统更新: sudo apt update sudo apt upgrade确保您的软件源列表中包含了正确的软件源。可以使用以下命令编辑软件源列表: sudo nano /etc/…...
![](https://www.ngui.cc/images/no-images.jpg)
Selector选择器在AspNetCore中的用法
Selector选择器在AspNetCore中的用法 背景 项目编辑过程中会选择其所属的上级项目,而上级项目在数据结构中是以ParentID的方式表达,而非Project类型,用户不会记录也不应该记录ID值,因此应提供Selector项目下拉框供用户选择。 但…...
![](https://img-blog.csdnimg.cn/d7e8ff4642ea4fd790db223118b92f1c.png)
anaconda3最新版安装|使用详情|Error: Please select a valid Python interpreter
Win11查看安装的Python路径及安装的库 anaconda3最新版安装|使用详情|Error: Please select a valid Python interpreter 介绍开源包管理系统和环境管理系统 ,包括多种语言的包安装,运行,更新,删除,最重要的是可以解…...
![](https://www.ngui.cc/images/no-images.jpg)
java八股文面试[多线程]——锁的分类
1.1 可重入锁、不可重入锁 Java中提供的synchronized,ReentrantLock,ReentrantReadWriteLock都是可重入锁。 重入:当前线程获取到A锁,在获取之后尝试再次获取A锁是可以直接拿到的。 不可重入:当前线程获取到A锁&…...
![](https://img-blog.csdnimg.cn/aa605302508b42d999f817dcc71628c3.png)
儿童安全门和围栏,以及游戏围栏等美国站要求的合规标准是什么?
儿童安全门和围栏 儿童安全门和围栏用于在门口(如门道)内设置围栏,或用作自支撑围栏,将幼儿可能在其中活动的区域围起来。这些商品可能由塑料、金属、乙烯树脂或木制组件等材料制成。此政策包括但不限于可扩展围栏、伸缩安全门和…...
![](https://www.ngui.cc/images/no-images.jpg)
kafka配合ElasticStack技术栈的搭配使用
今日内容: - kafka生产环境调优; - kafka配合ElasticStack技术栈的搭配使用; - zookeeper集群部署; - zookeeper的ACL; - zookeeper的调优; - PB级别项目; - ES8集群搭建/elk; (待定...) 订阅1个的topic: 老男孩: 10 多个不同的主题…...
![](https://images0.cnblogs.com/blog/705575/201503/012125201597047.png)
网站后台怎么/站长网站优化公司
一、方法重载: 1.两个函数同名,就互相构成方法的重载关系 2.重载的函数,必须跟其他函数之间具有不同的参数类型或参数个数 二、字段与属性 类的字段: 类里面是可以直接定义变量的,这些变量就叫类的字段,也叫…...
![](https://img-blog.csdnimg.cn/img_convert/3cdba8a0340945ce9eb32ef5426c09d3.png)
wordpress 没有样式表/百度推广总部客服投诉电话
前阵子,我和阿里的薪酬福利专家M同学聊了一下午,M同学做了9年薪酬,和我们吐槽了很多薪酬方面的现象,也道出了少有人关注的薪酬逻辑和常识。 这一次,我又找了一位阿里技术岗位的招聘专家T同学,从他的视角中…...
![](https://img-blog.csdnimg.cn/img_convert/5932f941e2f7fbd5d8700d820310ac83.png)
微信网站模板/百度移动端模拟点击排名
转行java和Web前端学哪个好?其实二者并没有好与坏之分,选择web前端还是Java主要看自己的兴趣爱好,以及技术倾向,不管是前端还是后端,都是高薪职业,都很有发展前景。 对于Java而言,JavaSE、Javaw…...
![](https://s1.51cto.com/images/blog/201801/10/9fc3755bf145aa725f2b36a8e029f91b.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
小公司网站如何做/浙江seo技术培训
今天东哥想用Scorpio Pro 5查一下猪场某人邮箱的密码,发现不太好使。决定自己写个自己用。代码如下 #!/usr/bin/python #-*- coding:utf-8 -*- #输入这一条就可以在Python脚本里面使用汉语注释!此脚本可以直接复制使用;while True: …...
![](https://img-blog.csdnimg.cn/ff36e1211ab8451eb3a29e5b17e15af6.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjU4NjcyMw==,size_16,color_FFFFFF,t_70#pic_center)
wordpress顶部添加图片/长尾关键词排名系统
电商项目实战之商品秒杀定时任务corn表达式实现方式基于注解基于接口实战秒杀系统秒杀系统关注问题秒杀架构设计商品上架获取当前秒杀商品获取当前商品的秒杀信息秒杀最终处理参考链接定时任务 corn表达式 定时查询秒杀活动 https://cron.qqe2.com/ 实现方式 基于注解 内容…...
![](/images/no-images.jpg)
企业做网站推广产品需要多少钱/拼多多商品关键词搜索排名
作者:瀚高PG实验室 (Highgo PG Lab)-瀚高大李 PostgreSQL是世界上功能最强大的开源数据库,在国内得到了越来越多机构和开发者的青睐和应用。随着PostgreSQL的应用越来越广泛,Oracle向PostgreSQL数据库的数据迁移需求也…...