前言

  这几天在学习如何使用自定义注解实现Excel格式数据导入导出,参考的还是若依框架里面的代码,由于是初学,所以照猫画虎呗,但是难受的是需要复制并根据自己项目修改作者自定义的工具类以及导入这些工具类的依赖包。由于吃了这个苦,我决定把这个艰辛的CV操作通过一张逻辑图来表达,方便我以后复用。下面证实开始介绍这个功能的实现,但是由于对项目中的只是很不了解,我这里简单实现,并简单讲解,深层次的代码我会给出,后续会继续运用讲解。整篇博客分为两个部分,一部分是数据的导出,一部分是数据的导入。本文项目链接:WomPlus: 结合若依项目对原始工单项目内容进行增强 (gitee.com)

1.所需要的依赖

  在进行项目前,我们需要导入依赖才能引用具体的功能,因此第一步就是导入依赖了,这里除了Excel需要的依赖,一些工具类的依赖也需要导入,为什么呢?因为作者的项目写的很细致,比如在咋们的java中自带有StringUtils工具类,但是作者细致地自己写了一个,可见其基础功能之深呀!

<!--Excel工具类依赖-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency> <!--常用工具类依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency> <!--io常用工具依赖-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency> <!--解决@ConfigurationProperties(prefix = "wo")的
Springboot Configuration Annotation Processor not found in classPath问题依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

所需依赖

2.修改配置文件

  引入依赖是第一步,修改配置文件就是我们的第二步了,在这里修改配置文件是因为在该功能的数据导入,作者是在自己自定义的RuoYiConfig中通过

@ConfigurationProperties(prefix = "wo")注解获取配置文件中profile属性值,即我们上传下载文件的地址。我这里使用的是properties为后缀的文件,配置文件为yaml的请根据自己文件类型修改
//添加自己的项目配置类WoConfig需要配置的项目名和导出Excel形式数据下载路径
#项目相关配置
wo.name=WO
wo.profile=G:/Desktop/base_study/project/myself_springboot_project/wom-plus/uploadPath

3.数据以Excel格式导出的Controller层

@PostMapping("/export")
@ResponseBody
public AjaxResult export(@RequestParam(value = "name", required = false) String username){
List<SysUser> list = userDetailsService.getUserListByUsername(username);
ExcelUtil<SysUser> util = new ExcelUtil<>(SysUser.class);
return util.exportExcel(list, "用户数据");
}
  在UserController的export()方法中,首先传入一个Class.class作为ExcelUtils类的参数来new一个ExcelUtils对象util,然后传递一个Class的list对象和表名称到util的exportExcel()方法中返回Class的Excel信息。

3.1 ExcelUtils的有参构造

//1.传入需要Excel导出类的Class.class,返回一个ExcelUtil<Class>对象
public ExcelUtil(Class<T> clazz)
{
this.clazz = clazz;
}

  这里就是传入一个Class类来创建该类的Excel对象,本篇博客围绕着SysUser类实现该功能的,因此这里传入SysUser.class

3.2 exportExcel(List<T>list, String sheetName)

public AjaxResult exportExcel(List<T> list, String sheetName)
{
return exportExcel(list, sheetName, StringUtils.EMPTY);
}

  在方法中,调用exportExcel(List<SysUser>list, String sheetName, String title)封装Excel格式数据导出

3.3 exportExcel(List<T> list, String sheetName, String title)

public AjaxResult exportExcel(List<T> list, String sheetName, String title)
{
this.init(list, sheetName, title, Type.EXPORT);
return exportExcel();
}

  本文方法首先初始化要创建的Excel表格,然后返回exportExcel()方法来讲要导出的数据写进前面的profile路径中

3.3.1 init(list, sheetName, title, Type.EXPORT)

public void init(List<T> list, String sheetName, String title, Type type)
{
if (list == null)
{
list = new ArrayList<T>();
}
this.list = list;
this.sheetName = sheetName;
this.type = type;
this.title = title;
//根据要导出Excel的实体类的字段创建字段Class类所需要的Excel字段
createExcelField();
createWorkbook();//创建一个工作薄
createTitle();//创建Excel第一行标题
createSubHead();//创建对象子列表名称
}

3.3.2 exportExcel()

public AjaxResult exportExcel()
{
OutputStream out = null;
try
{
writeSheet();//写入数据到Sheet
String filename = encodingFilename(sheetName);//编辑文件名
//getAbsoluteFile(filename)根据文件名称获取下载路径
//创建一个输出流
out = new FileOutputStream(getAbsoluteFile(filename));
wb.write(out);//写入Excel信息到该路径
return AjaxResult.success(filename);//返回导出成功信息
}
catch (Exception e)
{
log.error("导出Excel异常{}", e.getMessage());
throw new UtilException("导出Excel失败,请联系网站管理员!");
}
finally
{
IOUtils.closeQuietly(wb);//关闭工作薄对象输出流
IOUtils.closeQuietly(out);//关闭输出流
}
}

导出数据库数据到指定路径

3.4 @Excel和@Excels注解

package com.ku.wo.framework.aspectj.lang.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.math.BigDecimal;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import com.ku.wo.common.utils.poi.ExcelHandlerAdapter; /**
* 自定义导出Excel数据注解
*
* @author ruoyi
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)//作用在什么地方
public @interface Excel
{
/**
* 导出时在excel中排序
*/
public int sort() default Integer.MAX_VALUE; /**
* 导出到Excel中的名字.
*/
public String name() default ""; /**
* 日期格式, 如: yyyy-MM-dd
*/
public String dateFormat() default ""; /**
* 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
*/
public String dictType() default ""; /**
* 读取内容转表达式 (如: 0=男,1=女,2=未知)
*/
public String readConverterExp() default ""; /**
* 分隔符,读取字符串组内容
*/
public String separator() default ","; /**
* BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化)
*/
public int scale() default -1; /**
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
*/
public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; /**
* 导出时在excel中每个列的高度 单位为字符
*/
public double height() default 14; /**
* 导出时在excel中每个列的宽 单位为字符
*/
public double width() default 16; /**
* 文字后缀,如% 90 变成90%
*/
public String suffix() default ""; /**
* 当值为空时,字段的默认值
*/
public String defaultValue() default ""; /**
* 提示信息
*/
public String prompt() default ""; /**
* 设置只能选择不能输入的列内容.
*/
public String[] combo() default {}; /**
* 是否需要纵向合并单元格,应对需求:含有list集合单元格)
*/
public boolean needMerge() default false; /**
* 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写.
*/
public boolean isExport() default true; /**
* 另一个类中的属性名称,支持多级获取,以小数点隔开
*/
public String targetAttr() default ""; /**
* 是否自动统计数据,在最后追加一行统计数据总和
*/
public boolean isStatistics() default false; /**
* 导出类型(0数字 1字符串 2图片)
*/
public ColumnType cellType() default ColumnType.STRING; /**
* 导出列头背景颜色
*/
public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT; /**
* 导出列头字体颜色
*/
public IndexedColors headerColor() default IndexedColors.WHITE; /**
* 导出单元格背景颜色
*/
public IndexedColors backgroundColor() default IndexedColors.WHITE; /**
* 导出单元格字体颜色
*/
public IndexedColors color() default IndexedColors.BLACK; /**
* 导出字段对齐方式
*/
public HorizontalAlignment align() default HorizontalAlignment.CENTER; /**
* 自定义数据处理器
*/
public Class<?> handler() default ExcelHandlerAdapter.class; /**
* 自定义数据处理器参数
*/
public String[] args() default {}; /**
* 字段类型(0:导出导入;1:仅导出;2:仅导入)
*/
Type type() default Type.ALL; public enum Type
{
ALL(0), EXPORT(1), IMPORT(2);
private final int value; Type(int value)
{
this.value = value;
} public int value()
{
return this.value;
}
} public enum ColumnType
{
NUMERIC(0), STRING(1), IMAGE(2);
private final int value; ColumnType(int value)
{
this.value = value;
} public int value()
{
return this.value;
}
}
}
//@Excels
package com.ku.wo.framework.aspectj.lang.annotation; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* Excel注解集
*
* @author ruoyi
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Excels
{
Excel[] value();
}

@Excel和@Excels

3.5 运行结果

4.数据以Excel格式导出的Controller层

@PostMapping("/importData")
@ResponseBody
public AjaxResult importData(MultipartFile file) throws Exception {
ExcelUtil<SysUser> util = new ExcelUtil<>(SysUser.class);
List<SysUser> userList = util.importExcel(file.getInputStream());
String message = userDetailsService.importUser(userList);
return AjaxResult.success(message);
}
  在UserController的importData()方法中,首先传入一个Class.class作为ExcelUtils类的参数来new一个ExcelUtils对象util,然后传入一个要导入的Excel形式的表格数据作为utils.importData()函数的输入流is,importData()方法根据is返回Excel表格数据中的Class类的list对象,接着传入一个Class的list对象和一个关于是否支持更新数据库已有数据的布尔变量到importUser()方法中,该方法根据这两个参数来返回插入成功或失败的信息message,最后传入message到AjaxResult.success()方法返回导入数据成功消息。

4.1 importExcel(InputStream)

public List<T> importExcel(InputStream is) throws Exception
{
return importExcel(is, 0);
} public List<T> importExcel(InputStream is, int titleNum) throws Exception
{
return importExcel(StringUtils.EMPTY, is, titleNum);
} //核心实现代码
public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception
{
this.type = Type.IMPORT;
this.wb = WorkbookFactory.create(is);
List<T> list = new ArrayList<T>();
// 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet
Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);
if (sheet == null)
{
throw new IOException("文件sheet不存在");
}
boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook);
Map<String, PictureData> pictures;
if (isXSSFWorkbook)
{
pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb);
}
else
{
pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb);
}
// 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1
int rows = sheet.getLastRowNum(); if (rows > 0)
{
// 定义一个map用于存放excel列的序号和field.
Map<String, Integer> cellMap = new HashMap<String, Integer>();
// 获取表头
Row heard = sheet.getRow(titleNum);
for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++)
{
Cell cell = heard.getCell(i);
if (StringUtils.isNotNull(cell))
{
String value = this.getCellValue(heard, i).toString();
cellMap.put(value, i);
}
else
{
cellMap.put(null, i);
}
}
// 有数据时才处理 得到类的所有field.
List<Object[]> fields = this.getFields();
Map<Integer, Object[]> fieldsMap = new HashMap<Integer, Object[]>();
for (Object[] objects : fields)
{
Excel attr = (Excel) objects[1];
Integer column = cellMap.get(attr.name());
if (column != null)
{
fieldsMap.put(column, objects);
}
}
for (int i = titleNum + 1; i <= rows; i++)
{
// 从第2行开始取数据,默认第一行是表头.
Row row = sheet.getRow(i);
// 判断当前行是否是空行
if (isRowEmpty(row))
{
continue;
}
T entity = null;
for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet())
{
Object val = this.getCellValue(row, entry.getKey()); // 如果不存在实例则新建.
entity = (entity == null ? clazz.newInstance() : entity);
// 从map中得到对应列的field.
Field field = (Field) entry.getValue()[0];
Excel attr = (Excel) entry.getValue()[1];
// 取得类型,并根据对象类型设置值.
Class<?> fieldType = field.getType();
if (String.class == fieldType)
{
String s = Convert.toStr(val);
if (StringUtils.endsWith(s, ".0"))
{
val = StringUtils.substringBefore(s, ".0");
}
else
{
String dateFormat = field.getAnnotation(Excel.class).dateFormat();
if (StringUtils.isNotEmpty(dateFormat))
{
val = parseDateToStr(dateFormat, val);
}
else
{
val = Convert.toStr(val);
}
}
}
else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val)))
{
val = Convert.toInt(val);
}
else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val)))
{
val = Convert.toLong(val);
}
else if (Double.TYPE == fieldType || Double.class == fieldType)
{
val = Convert.toDouble(val);
}
else if (Float.TYPE == fieldType || Float.class == fieldType)
{
val = Convert.toFloat(val);
}
else if (BigDecimal.class == fieldType)
{
val = Convert.toBigDecimal(val);
}
else if (Date.class == fieldType)
{
if (val instanceof String)
{
val = DateUtils.parseDate(val);
}
else if (val instanceof Double)
{
val = DateUtil.getJavaDate((Double) val);
}
}
else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
{
val = Convert.toBool(val, false);
}
if (StringUtils.isNotNull(fieldType))
{
String propertyName = field.getName();
if (StringUtils.isNotEmpty(attr.targetAttr()))
{
propertyName = field.getName() + "." + attr.targetAttr();
}
else if (StringUtils.isNotEmpty(attr.readConverterExp()))
{
val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
}
else if (StringUtils.isNotEmpty(attr.dictType()))
{
val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator());
}
else if (!attr.handler().equals(ExcelHandlerAdapter.class))
{
val = dataFormatHandlerAdapter(val, attr);
}
else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures))
{
PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey());
if (image == null)
{
val = "";
}
else
{
byte[] data = image.getData();
val = FileUtils.writeImportBytes(data);
}
}
ReflectUtils.invokeSetter(entity, propertyName, val);
}
}
list.add(entity);
}
}
return list;
}

importExcel

  该方法主要是通过传入的文件获取Excel表格中的数据,这里是真正导入数据的核心,我暂时还没想清楚。

4.2 importUser(InputStream)

String importUser(List<SysUser> userList);
@Override
public String importUser(List<SysUser> userList) {
//此处的isNull判断的是一个对象,此时我传入的是一个用户list对象
if (StringUtils.isNull(userList) || userList.size() == 0){
throw new ServiceException("导入用户数据不能为空!");
}
int successNum = 0;
//StringBuilder字符拼接工具类,是一个可变类不安全,StringBuffer是一个不可变的安全字符拼接工具类
StringBuilder successMsg = new StringBuilder();
for (SysUser user: userList) {
//因为前面验证了该list对象不为空,故直接插入
userMapper.insertUser(user);
successNum++;
successMsg.append("<br/>" + successNum + "、账号 " + user.getUsername() + " 导入成功");
}
successMsg.insert(0, "恭喜你,数据已经全部导入成功!共" + successNum + "条,数据如下:");
return successMsg.toString();
}

Excel表格数据插入到对应数据库的数据表

  该方法实现将Excel中获取的数据导入到对应数据库的数据表中,并返回导入成功的响应信息。

4.3 运行结果

5.参考项目连链接

RuoYi: 基于SpringBoot的权限管理系统 易读易懂、界面简洁美观。 核心技术采用Spring、MyBatis、Shiro没有任何其它重度依赖。直接运行即可用 (gitee.com)

2.自定义@Excel注解实现数据Excel形式导入导出的更多相关文章

  1. Thinkphp框架下PHPExcel实现Excel数据的批量化导入导出

    第一步:下载官方的PHPExcel文件,下载地址https://github.com/PHPOffice/PHPExcel 第二步:解压打开,将PHPExcel\Classes\全部文件拷贝到thin ...

  2. Postgresql数据库数据简单的导入导出

    Postgresql数据库数据简单的导入导出 博客分类: DataBase postgres  命令操作: 数据的导出:pg_dump -U postgres(用户名)  (-t 表名)  数据库名( ...

  3. Java实现PDF和Excel生成和数据动态插入以及导出

    一.序言 Excel.PDF的导出.导入是我们工作中经常遇到的一个问题,刚好今天公司业务遇到了这个问题,顺便记个笔记以防下次遇到相同的问题而束手无策. 公司有这么两个需求: 需求一.给了一个表单,让把 ...

  4. .net实现与excel的数据交互、导入导出

    应该说,一套成熟的基于web的管理系统,与用户做好的excel表格进行数据交互是一个不可或缺的功能,毕竟,一切以方便客(jin)户(qian)为宗旨. 本人之前从事PHP的开发工作,熟悉PHP的都应该 ...

  5. Magicodes.IE 在100万数据量下导入导出性能测试

    原文作者:HueiFeng 前言 目前Magicodes.IE更新到了2.2.3,感谢大家的支持,同时建议大家在使用过程中如果遇到一些问题或者说需要一些额外的功能可以直接提issues,当然更建议大家 ...

  6. Oracle数据泵的导入导出

    说明:数据泵技术是Oracle Database 10g 中的新技术,它比原来导入/导出(imp,exp)技术快15-45倍.速度的提高源于使用了并行技术来读写导出转储文件. expdp导出 1.以s ...

  7. PLSQL_数据泵Datapump导入导出数据IMPDP / EXPDP(概念)(Oracle数据导入导出工具)(转)

    一.摘要 在平常备库和数据库迁移的时候,当遇到大的数据库的时候在用exp的时候往往是需要好几个小时,耗费大量时间.oracle10g以后可以用expdp来导出数据库花费的时间要远小于exp花费的时间, ...

  8. Oracle数据库采用数据泵方式导入导出数据

    特别说明:Oralce的数据泵导入导出技术只能用在数据库服务器上,在只有客户端的机器上是无法使用数据泵技术的. 1.创建备份文件目录  mkdir d:\dmp 2.在Oralce中注册该目录,将目录 ...

  9. oracle11g-R2数据库的逻辑备份(数据泵的导入导出)

    一.环境: server1迁移到server2 server1: 服务器号:201 系统:Windows server 2008 R2 x64 IP地址:192.168.2.201 oracle数据库 ...

  10. Microsoft SQL Server 数据量大 导入导出 问题汇总

    问题一: 今天拿到一份有近百万条数据的Excel要导到数据库里面,我先在本地(2014)用自带Excel,然后生成脚本文件去服务器(2008)上执行:文件SQL打开不了. 解决方法: 用自带的sqlc ...

随机推荐

  1. js- throw

    // Create an object type UserExceptionfunction UserException (message){ this.message=message; this.n ...

  2. DEM高程数据下载资源

    最近发现了几个比较好的DEM高程数据免费下载资源,遂总结一下. clouldRF(https://cloudrf.com/terrain%20data)官方网站有说明其支持的地形数据来源,主要包括如下 ...

  3. JAVA 在开发中如何选择集合实现类

    先判断存储地类型(一组对象[单列]或者键值对[多列]) 一组对象:Collection接口 允许重复:list 增删多:LinkedList[底层维护了一个双向链表] 改查多;ArratList[底层 ...

  4. jdbc连接数据库access denied for user 'root'@'localhost'(using password:YES)

    navicat可以进行连接,一般原因为mysql未启用远程连接 以下为解决方案 解决方法-更新用户加密方式: MySQL [mysql]> ALTER USER 'root'@'%' IDENT ...

  5. CF1430

    CF1430 那个博客搭好遥遥无期. A: 看代码. #include<bits/stdc++.h> using namespace std; int main() { int t;sca ...

  6. http请求的方法

    1.OPTIONS 返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送'*'的请求来测试服务器的功能性. 2.HEAD 向服务器索要与GET请求相一致的相应,只不过响应体将不 ...

  7. java 线程池 带返回值

    import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import ja ...

  8. Rsync+NFS实战,解决NFS单点问题

    1.环境准备 主机 ⻆⾊ ip web01 NFS客户端.RSYNC客户端 172.16.1.7 nfs NFS服务端.RSYNC客户端 172.16.1.31 backup NFS服务端.RSYNC ...

  9. 深入理解css 笔记(9)

    模块化 CSS 是指把页面分割成不同的组成部分,这些组成部分可以在多种上下文中重复使用,并且互相之间没有依赖关系.最终目的是,当我们修改其中一部分 css 时,不会对其他部分产生意料之外的影响.    ...

  10. Unity递归查找子物体