上一篇博客介绍了如何完整的基于ssm搭建一个可用于实战开发的项目,本文开始将用此项目用来开发若干idea。本文就先小试牛刀开发基于ssm框架实现poi导入导出excel文件,凭良心讲,这一需求其实在目前企业级应用开发中几乎都用得到,那么本文我将基于自己在项目中遇到的需求来介绍如何实现poi导入导出。如有相关疑问可来此交流:java开源技术交流群-583522159-我是debug (视频教程地址:https://edu.csdn.net/course/detail/8894 欢迎支持!!)
首先我们将基于这样的场景:某部门需要经常导出目前仓库具有产品列表;或者偶尔隔一段时间需要导入一些产品到系统数据库中用于其他业务使用!为了方便展示,首先我们建立一个表:产品表product,建表语句如下:
CREATE TABLE `product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL COMMENT '名称',
`unit` varchar(20) DEFAULT NULL COMMENT '单位',
`price` double(15,2) DEFAULT NULL COMMENT '单价',
`stock` double(11,0) DEFAULT NULL COMMENT '库存量',
`remark` varchar(1000) DEFAULT NULL,
`purchase_date` date DEFAULT NULL COMMENT '采购日期',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='产品信息表';
然后我们使用mybatis逆向工具生成实体类的相关文件,依次是实体类Product.java,ProductMapper.java以及ProductMapper.xml文件
package com.debug.steadyjack.model;
import java.util.Date;
public class Product {
private Integer id;
private String name;
private String unit;
private Double price;
private Double stock;
private String remark;
private Date purchaseDate;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name == null ? null : name.trim();
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit == null ? null : unit.trim();
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark == null ? null : remark.trim();
}
public Date getPurchaseDate() {
return purchaseDate;
}
public void setPurchaseDate(Date purchaseDate) {
this.purchaseDate = purchaseDate;
}
public Double getStock() {
return stock;
}
public void setStock(Double stock) {
this.stock = stock;
}
@Override
public String toString() {
return "Product [id=" + id + ", name=" + name + ", unit=" + unit
+ ", price=" + price + ", stock=" + stock + ", remark="
+ remark + ", purchaseDate=" + purchaseDate + "]";
}
}
package com.debug.steadyjack.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.debug.steadyjack.model.Product;
public interface ProductMapper {
int deleteByPrimaryKey(Integer id);
int insert(Product record);
int insertSelective(Product record);
Product selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(Product record);
int updateByPrimaryKey(Product record);
List<Product> selectAll(@Param("name") String name);
void insertBatch(@Param("dataList") List<Product> dataList);
}
<?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.debug.steadyjack.mapper.ProductMapper" >
<resultMap id="BaseResultMap" type="com.debug.steadyjack.model.Product" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="unit" property="unit" jdbcType="VARCHAR" />
<result column="price" property="price" jdbcType="DOUBLE" />
<result column="stock" property="stock" jdbcType="DOUBLE" />
<result column="remark" property="remark" jdbcType="VARCHAR" />
<result column="purchase_date" property="purchaseDate" jdbcType="DATE" />
</resultMap>
<sql id="Base_Column_List" >
id, name, unit, price, stock, remark, purchase_date
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
<include refid="Base_Column_List" />
from product
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
delete from product
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" parameterType="com.debug.steadyjack.model.Product" >
insert into product (id, name, unit,
price, stock, remark,
purchase_date)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{unit,jdbcType=VARCHAR},
#{price,jdbcType=DOUBLE}, #{stock,jdbcType=DOUBLE}, #{remark,jdbcType=VARCHAR},
#{purchaseDate,jdbcType=DATE})
</insert>
<insert id="insertSelective" parameterType="com.debug.steadyjack.model.Product" >
insert into product
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="id != null" >
id,
</if>
<if test="name != null" >
name,
</if>
<if test="unit != null" >
unit,
</if>
<if test="price != null" >
price,
</if>
<if test="stock != null" >
stock,
</if>
<if test="remark != null" >
remark,
</if>
<if test="purchaseDate != null" >
purchase_date,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="id != null" >
#{id,jdbcType=INTEGER},
</if>
<if test="name != null" >
#{name,jdbcType=VARCHAR},
</if>
<if test="unit != null" >
#{unit,jdbcType=VARCHAR},
</if>
<if test="price != null" >
#{price,jdbcType=DOUBLE},
</if>
<if test="stock != null" >
#{stock,jdbcType=DOUBLE},
</if>
<if test="remark != null" >
#{remark,jdbcType=VARCHAR},
</if>
<if test="purchaseDate != null" >
#{purchaseDate,jdbcType=DATE},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.debug.steadyjack.model.Product" >
update product
<set >
<if test="name != null" >
name = #{name,jdbcType=VARCHAR},
</if>
<if test="unit != null" >
unit = #{unit,jdbcType=VARCHAR},
</if>
<if test="price != null" >
price = #{price,jdbcType=DOUBLE},
</if>
<if test="stock != null" >
stock = #{stock,jdbcType=DOUBLE},
</if>
<if test="remark != null" >
remark = #{remark,jdbcType=VARCHAR},
</if>
<if test="purchaseDate != null" >
purchase_date = #{purchaseDate,jdbcType=DATE},
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="com.debug.steadyjack.model.Product" >
update product
set name = #{name,jdbcType=VARCHAR},
unit = #{unit,jdbcType=VARCHAR},
price = #{price,jdbcType=DOUBLE},
stock = #{stock,jdbcType=DOUBLE},
remark = #{remark,jdbcType=VARCHAR},
purchase_date = #{purchaseDate,jdbcType=DATE}
where id = #{id,jdbcType=INTEGER}
</update>
<!-- 查询所有产品 -->
<select id="selectAll" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/> FROM product WHERE 1=1
<if test="name!=null and name!='' ">
AND `name` LIKE CONCAT('%', #{name}, '%')
</if>
</select>
<!-- 批量插入数据 -->
<insert id="insertBatch" parameterType="java.util.List" useGeneratedKeys="true">
insert into product (name, unit,price, stock, remark,purchase_date)
<foreach collection="dataList" item="data" index="index" separator=",">
values (#{data.name,jdbcType=VARCHAR}, #{data.unit,jdbcType=VARCHAR},#{data.price,jdbcType=DOUBLE},
#{data.stock,jdbcType=DOUBLE}, #{data.remark,jdbcType=VARCHAR}, #{data.purchaseDate,jdbcType=DATE})
</foreach>
</insert>
</mapper>
其中,我在上面以及实现了:查询产品列表(也可用于搜索)、批量插入产品数据 的sql-将分别用于导出以及导入
然后,我们就得开发导出以及导入的controller功能了,PoiController.java:
package com.debug.steadyjack.controller;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import com.debug.steadyjack.dto.BaseResponse;
import com.debug.steadyjack.enums.StatusCode;
import com.debug.steadyjack.mapper.ProductMapper;
import com.debug.steadyjack.model.Product;
import com.debug.steadyjack.service.PoiService;
import com.debug.steadyjack.utils.ExcelBeanUtil;
import com.debug.steadyjack.utils.ExcelUtil;
import com.debug.steadyjack.utils.WebUtil;
/**
* 导入导出controller
* @author zhonglinsen
*
*/
@Controller
public class PoiController {
private static final Logger log=LoggerFactory.getLogger(PoiController.class);
private static final String prefix="poi";
@Autowired
private ProductMapper productMapper;
@Autowired
private PoiService poiService;
@Value("${poi.excel.sheet.name}")
private String sheetProductName;
@Value("${poi.excel.file.name}")
private String excelProductName;
/**
* 获取产品列表-可用于搜索
* @param name
* @return
*/
@RequestMapping(value=prefix+"/list",method=RequestMethod.GET)
@ResponseBody
public BaseResponse<List<Product>> list(String name){
BaseResponse<List<Product>> response=new BaseResponse<List<Product>>(StatusCode.Success);
try {
List<Product> products=productMapper.selectAll(name);
response.setData(products);
} catch (Exception e) {
log.error("获取产品列表发生异常: ",e.fillInStackTrace());
}
return response;
}
/**
* 下载excel
* @param response
* @return
*/
@RequestMapping(value=prefix+"/excel/export",method=RequestMethod.GET)
public @ResponseBody String exportExcel(HttpServletResponse response,String search){
try {
List<Product> products=productMapper.selectAll(search);
String[] headers=new String[]{"id编号","名称","单位","单价","库存量","采购日期","备注信息"};
List<Map<Integer, Object>> dataList=ExcelBeanUtil.manageProductList(products);
log.info("excel下载填充数据: {} ",dataList);
Workbook wb=new HSSFWorkbook();
ExcelUtil.fillExcelSheetData(dataList, wb, headers, sheetProductName);
WebUtil.downloadExcel(response, wb, excelProductName);
return excelProductName;
} catch (Exception e) {
log.error("下载excel 发生异常:",e.fillInStackTrace());
}
return null;
}
/**
* 上传excel导入数据
* @param request
* @return
* 1、不要忘了支持springmvc上传文件的配置
*/
@SuppressWarnings("rawtypes")
@RequestMapping(value=prefix+"/excel/upload",method=RequestMethod.POST,consumes=MediaType.MULTIPART_FORM_DATA_VALUE)
@ResponseBody
public BaseResponse uploadExcel(MultipartHttpServletRequest request){
BaseResponse response=new BaseResponse<>(StatusCode.Success);
try {
String version=request.getParameter("version");
MultipartFile file=request.getFile("productFile");
if (StringUtils.isEmpty(version) || file==null) {
return new BaseResponse<>(StatusCode.Invalid_Param);
}
log.debug("版本号:{} file:{} ",version,file);
HSSFWorkbook wb=new HSSFWorkbook(file.getInputStream());
List<Product> products=poiService.readExcelData(wb);
productMapper.insertBatch(products);
} catch (Exception e) {
log.error("上传excel导入数据 发生异常:",e.fillInStackTrace());
return new BaseResponse<>(StatusCode.System_Error);
}
return response;
}
}
在上面涉及到了一些工具类,这些工具类在上篇博文已经贴出来了,自行去找即可!在这里重点再贴一个工具类ExcelBeanUtil.java:专门用于处理某些“业务实体”的excel工具
package com.debug.steadyjack.utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.debug.steadyjack.model.Product;
/**
* 导入excel bean数据工具类
* @author zhonglinsen
*
*/
public class ExcelBeanUtil {
/**
* 处理产品列表 塞入list-map 等待塞入excel的workbook进行处理
* @param products
* @return
*/
public static List<Map<Integer, Object>> manageProductList(final List<Product> products){
List<Map<Integer, Object>> dataList=new ArrayList<>();
if (products!=null && products.size()>0) {
int length=products.size();
Map<Integer, Object> dataMap;
Product bean;
for (int i = 0; i < length; i++) {
bean=products.get(i);
dataMap=new HashMap<>();
dataMap.put(0, bean.getId());
dataMap.put(1, bean.getName());
dataMap.put(2, bean.getUnit());
dataMap.put(3, bean.getPrice());
dataMap.put(4, bean.getStock());
dataMap.put(5, bean.getPurchaseDate());
dataMap.put(6, bean.getRemark());
dataList.add(dataMap);
}
}
return dataList;
}
}
接下来介绍一下导入导出的思路:
1、导出:对于导出而已,首先我们得明确一点就是最终数据是导出到excel中,而观察excel每个sheet发现,是一个 “二维的世界”,即“矩阵”格式的数据: 头部行是你需要命名的那些字段名,之后的每一行的每一列都是那些字段对应的实际的值,如下图所示:
所以,按照这个逻辑,首先我们需要定好头部行的字段名(当然啦,你也可以配置在配置文件),在这里我是直接建立一个字符串数组存储了,然后你还需要获取你需要导出到excel的productList。按照上面的截图,你会发现,其实每一行就是 一个Object,而多个Object构成了一个list,即List<Object>,然后,这个Object需要满足productList中的每个product的每个字段可以对应到 “头部行的每个字段中”,即需要实现一一映射:即Object应当用map来充当。所以我们想办法如何结合“头部行”将 productList转为 List<Map<Integer,Object>>。即下面这段代码可以实现:
/**
* 处理产品列表 塞入list-map 等待塞入excel的workbook进行处理
* @param products
* @return
*/
public static List<Map<Integer, Object>> manageProductList(final List<Product> products){
List<Map<Integer, Object>> dataList=new ArrayList<>();
if (products!=null && products.size()>0) {
int length=products.size();
Map<Integer, Object> dataMap;
Product bean;
for (int i = 0; i < length; i++) {
bean=products.get(i);
dataMap=new HashMap<>();
dataMap.put(0, bean.getId());
dataMap.put(1, bean.getName());
dataMap.put(2, bean.getUnit());
dataMap.put(3, bean.getPrice());
dataMap.put(4, bean.getStock());
dataMap.put(5, bean.getPurchaseDate());
dataMap.put(6, bean.getRemark());
dataList.add(dataMap);
}
}
return dataList;
}
之后就可以将“头部行”以及“转化后的List<Map<Integer,Object>>”一起填充到excel中,即WorkBook,即上篇博文中介绍的ExcelUtil的部分代码:
private static final Logger log=LoggerFactory.getLogger(ExcelUtil.class);
private static final String dateFormat="yyyy-MM-dd";
private static final SimpleDateFormat simpleDateFormat=new SimpleDateFormat(dateFormat);
/**
* excel sheet填充数据
* @param dataList
* @param wb
* @param headers
* @param sheetName
*/
public static void fillExcelSheetData(List<Map<Integer, Object>> dataList,Workbook wb,String[] headers,String sheetName){
Sheet sheet=wb.createSheet(sheetName);
//TODO:创建sheet的第一行数据-即excel的头部信息
Row headerRow=sheet.createRow(0);
for(int i=0;i<headers.length;i++){
headerRow.createCell(i).setCellValue(headers[i]);
}
//TODO:从第二行开始塞入真正的数据列表
int rowIndex=1;
Row row;
Object obj;
for(Map<Integer, Object> rowMap:dataList){
try {
row=sheet.createRow(rowIndex++);
for(int i=0;i<headers.length;i++){
obj=rowMap.get(i);
if (obj==null) {
row.createCell(i).setCellValue("");
}else if (obj instanceof Date) {
String tempDate=simpleDateFormat.format((Date)obj);
row.createCell(i).setCellValue((tempDate==null)?"":tempDate);
}else {
row.createCell(i).setCellValue(String.valueOf(obj));
}
}
} catch (Exception e) {
log.debug("excel sheet填充数据 发生异常: ",e.fillInStackTrace());
}
}
}
最后调用WebUtil直接往输出流塞workbook即可-即excel文件!
直接访问:localhost:8090/ssm_poi/poi/excel/export.do 即可下载
2、导入的话,就比较简单:读取excel->构建workbook->读取excel中sheet数据并入List<Product>->最后是批量插入。其实核心在于 “读取excel中sheet数据并入list”,即PoiService的代码:
package com.debug.steadyjack.service;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.debug.steadyjack.model.Product;
import com.debug.steadyjack.utils.DateUtil;
import com.debug.steadyjack.utils.ExcelUtil;
@Service
public class PoiService {
private static final Logger log=LoggerFactory.getLogger(PoiService.class);
/**
* 读取excel数据
* @param wb
* @return
* @throws Exception
*/
public List<Product> readExcelData(Workbook wb) throws Exception{
Product product=null;
List<Product> products=new ArrayList<Product>();
Row row=null;
int numSheet=wb.getNumberOfSheets();
if (numSheet>0) {
for(int i=0;i<numSheet;i++){
Sheet sheet=wb.getSheetAt(i);
int numRow=sheet.getLastRowNum();
if (numRow>0) {
for(int j=1;i<numRow;i++){
//TODO:跳过excel sheet表格头部
row=sheet.getRow(j);
product=new Product();
String name=ExcelUtil.manageCell(row.getCell(1), null);
String unit=ExcelUtil.manageCell(row.getCell(2), null);
Double price=Double.valueOf(ExcelUtil.manageCell(row.getCell(3), null));
String stock=ExcelUtil.manageCell(row.getCell(4), null);
String remark=ExcelUtil.manageCell(row.getCell(6), null);
product.setName(name);
product.setUnit(unit);
product.setPrice(price);
product.setStock(Double.valueOf(stock));
String value=ExcelUtil.manageCell(row.getCell(5), "yyyy-MM-dd");
product.setPurchaseDate(DateUtil.strToDate(value, "yyyy-MM-dd"));
product.setRemark(remark);
products.add(product);
}
}
}
}
log.info("获取数据列表: {} ",products);
return products;
}
}
在上面需要用到excelUtil,上一篇找即可!
然后用postman模拟一下即可实现,其中文件的上传可以用你上面导出来的excel,然后上传即可,如下图:
在这期间,我构建了一个通用 与前端异步交互时 响应信息类BaseResponse.java:
package com.debug.steadyjack.dto;
import com.debug.steadyjack.enums.StatusCode;
public class BaseResponse<T> {
private Integer code;
private String msg;
private T data;
public BaseResponse() {
super();
}
public BaseResponse(StatusCode statusCode) {
this.code=statusCode.getCode();
this.msg=statusCode.getMsg();
}
public BaseResponse(StatusCode statusCode,T data) {
this.code=statusCode.getCode();
this.msg=statusCode.getMsg();
this.data = data;
}
public BaseResponse(Integer code, String msg, T data) {
super();
this.code = code;
this.msg = msg;
this.data = data;
}
public BaseResponse(Integer code, String msg) {
super();
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
状态码类StatusCode.java:
package com.debug.steadyjack.enums;
/**
* 通用状态码enum
* @author zhonglinsen
*
*/
public enum StatusCode {
Success(0,"成功"),
Fail(-1,"失败"),
Invalid_Param(1001,"无效的参数"),
System_Error(1002,"系统错误");
private Integer code;
private String msg;
private StatusCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
最后附上项目中后端代码的目录结构:
至此,这个ssm版本的poi导入导出excel就介绍到这里了。至于代码,几乎都贴在这篇博文以及上篇博文了。源码,加群583522159即可领取!!!另外我还录制成了一个视频以及将思路整理成为一个文档以更好的理解,如果需要可付给我一杯咖啡的费用找我要1974544863!
总结:
1、后面会实现根据上线的excel自动判断用HSSFWorkBook还是XSSFWorkBook来区分2003还是2007(网上有些童鞋是根据后缀名来区分,这很明显是错误的:如果我改了一个实质是2007的文档的后缀名为.xls,那就报错了!)
2、整合界面来看效果!