目錄
前言
平常的功能大家應該都會用到導入導出excel的功能,比如通過讀excel的方式將excel的數據導入到數據庫中。當然實現的方式有很多,今天我介紹的是利用阿里開源的easyexcel項目來完成功能。大家也可以自己看easyexcel的文檔進行開發。當然這裏因爲剛好工作的時候用到了,所以將自己寫的demo分享出來,大家就可以更快節省時間完成功能,大家可以參考,也可以直接拿來用,實際在你們自己的開發過程當中適當修改。
EasyExcel是一個基於Java的簡單、省內存的讀寫Excel的開源項目。在儘可能節約內存的情況下支持讀寫百M的Excel。
github地址:https://github.com/alibaba/easyexcel
一、引入easyexcel的maven
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
首先springBoot項目結構大家看一下,除了最簡單的三層架構,dao、service和controller,這裏還需要用到listener
二、讀取excel代碼示例
1、bean需要和excel的列對應
可以通過index和name將bean和excel的列對應起來,但是官方不建議index和name同時夾雜在一起用,顧名思義,在一個bean中要麼就是用index,要麼就是用name對應
eg:@ExcelProperty(index = 2)
eg:@ExcelProperty("日期標題")
demo
package com.xxx.xxxx.xxxx;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* Created by yjl on 2019/12/25.
*/
@Data
public class RoomMonitorCoverDataBean {
private String TIME_ID; //時間
@ExcelProperty(index = 0)
private String PROV_NAME; //省
@ExcelProperty(index = 1)
private String AREA_NAME; //地市
@DateTimeFormat("yyyy年MM月")
@ExcelProperty(index = 2)
private String MONTH_DESC; //月份,DateTimeFormat表示對日期進行格式化,不要的可以去掉
public String getTIME_ID() {
return TIME_ID;
}
public void setTIME_ID(String TIME_ID) {
this.TIME_ID = TIME_ID;
}
public String getPROV_NAME() {
return PROV_NAME;
}
public void setPROV_NAME(String PROV_NAME) {
this.PROV_NAME = PROV_NAME;
}
public String getAREA_NAME() {
return AREA_NAME;
}
public void setAREA_NAME(String AREA_NAME) {
this.AREA_NAME = AREA_NAME;
}
public String getMONTH_DESC() {
return MONTH_DESC;
}
public void setMONTH_DESC(String MONTH_DESC) {
this.MONTH_DESC = MONTH_DESC;
}
}
2、Controller層
很簡單,就注入service,引用service的方法,我們在service裏進行入參的判斷或者其他業務處理等,這裏我就直接放一個impl的了,interface是一樣的名字,放一個沒有實現的方法就可以了,當然你也可以不要那個interface
demo
@RequestMapping("/mrePortController")
public class MrePortControllerCSVImpl implements IMrePortControllerCSV {
@Autowired
private MrePortServiceCSV mrePortServiceCSV;
@RequestMapping(value = "/saveRoomMonitorCoverData", method = RequestMethod.POST)
@Override
public JSONObject saveRoomMonitorCoverData(MultipartFile file, String timeType, String userArea) {
return mrePortServiceCSV.saveRoomMonitorCoverData(file, timeType,userArea);
}
}
3、service層
這裏我們進行邏輯判斷,可以對入參或者其他一些東西進行判斷,
主要功能是
// 將excel表中的數據入庫
// 有個很重要的點 xxxxxListener 不能被spring管理,要每次讀取excel都要new,然後裏面用到spring可以構造方法傳進去
// 這裏 需要指定讀用哪個class去讀,默認讀取第一個sheet 文件流會自動關閉
EasyExcel.read(file.getInputStream(), RoomMonitorCoverDataBean.class, new RoomMonitorCoverDataListener(mrePortDao,tableName,timeType)).sheet().doRead();
demo
@Component
public class MrePortServiceCSVImpl implements MrePortServiceCSV {
@Autowired
private MrePortDao mrePortDao;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public JSONObject saveRoomMonitorCoverData(MultipartFile file, String timeType,String userArea) {
JSONObject jo = new JSONObject();
try {
//可以進行必傳參數校驗等
//這裏省略不寫了
//導入示例:excel名稱:小小魚兒小小林_浙江201912.xlsx
String fileName = file.getOriginalFilename().replaceAll("\\.xlsx|\\.xls","");
System.out.println("file.getName():"+file.getName()+",file.getOriginalFilename():"+file.getOriginalFilename());
String[] split = fileName.split("_");
String file_tableName = split[0]; //文件名稱表名稱
String file_areaNameAndDate = split[1]; //文件名稱地市和月份 eg:浙江201912
//String file_version = split[2]; //文件名稱版本號
if ("小小魚兒小小林".equals(file_tableName)){ //只能上傳該名稱的文件
String file_areaName = file_areaNameAndDate.substring(0, 2); //文件名稱地市
String file_date = file_areaNameAndDate.substring(2); //文件名稱月份
if (userArea.equals(file_areaName)){ //只能上傳自己權限範圍內的地市數據
//Long currentDate = Long.parseLong(getCurrentDate("yyyyMM"));
String currentDate = getCurrentDate("yyyyMM");
if (currentDate.equals(file_date)){ //只能上傳當月的數據
String tableName = "xxxxxxxxx";
// 將excel表中的數據入庫
// 有個很重要的點 xxxxxListener 不能被spring管理,要每次讀取excel都要new,然後裏面用到spring可以構造方法傳進去
// 這裏 需要指定讀用哪個class去讀,默認讀取第一個sheet 文件流會自動關閉
EasyExcel.read(file.getInputStream(), RoomMonitorCoverDataBean.class, new RoomMonitorCoverDataListener(mrePortDao,tableName,timeType)).sheet().doRead();
jo.put("msg","導入成功");
jo.put("resultCode",0);
jo.put("response","success");
}else {
jo.put("msg","您只能上傳當月"+currentDate+"的數據");
jo.put("resultCode",-1);
jo.put("response","");
return jo;
}
}else {
jo.put("msg","您暫時沒有權限上傳"+file_areaName+"的數據");
jo.put("resultCode",-1);
jo.put("response","");
return jo;
}
}else {
jo.put("msg","請選擇《小小魚兒小小林》文件再上傳");
jo.put("resultCode",-1);
jo.put("response","");
return jo;
}
}catch (IOException i){
i.printStackTrace();
jo.put("msg","IOException,請重試");
jo.put("resultCode",-1);
jo.put("response","");
} catch (Exception e){
e.printStackTrace();
jo.put("msg","導入異常,請重試");
jo.put("resultCode",-1);
jo.put("response","");
}
return jo;
}
}
4、listener層
注意這個不是mvc的三層架構,這裏加listener是因爲easyexcel需要用到,實際上解析excel的過程就是在這裏實行的,如果要對解析出來的每一行數據或者其中的一列進行另外的判斷或者做其他處理,是要到這個類裏面寫邏輯
RoomMonitorCoverDataListener.java
demo
/**
* Created by yjl on 2019/12/27.公衆號:zygxsq
*/
public class RoomMonitorCoverDataListener extends AnalysisEventListener<RoomMonitorCoverDataBean> {
private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
private static final int BATCH_COUNT = 1000; //每隔1000條存數據庫,然後清理list ,方便內存回收
List<RoomMonitorCoverDataBean> list = new ArrayList<RoomMonitorCoverDataBean>();
MrePortDao mrePortDao;
String timeType;
String tableName;
/**
* 如果使用了spring,請使用有參構造方法。每次創建Listener的時候需要把spring管理的類傳進來
*/
public RoomMonitorCoverDataListener(){}
public RoomMonitorCoverDataListener(MrePortDao mrePortDao) {
this.mrePortDao = mrePortDao;
}
public RoomMonitorCoverDataListener(MrePortDao mrePortDao,String timeType) {
this.mrePortDao = mrePortDao;
this.timeType = timeType;
}
public RoomMonitorCoverDataListener(MrePortDao mrePortDao,String tableName,String timeType) {
this.mrePortDao = mrePortDao;
this.tableName = tableName;
this.timeType = timeType;
}
/**
* 這個每一條數據解析都會來調用
* @param data
* @param analysisContext
*/
@Override
public void invoke(RoomMonitorCoverDataBean data, AnalysisContext analysisContext) {
LOGGER.info("解析到一條數據:{}", JSON.toJSONString(data));
//------------------begin------------------------
// 這裏對excel表裏的數據進行邏輯處理、篩選等,如果不需要對錶裏的數據進行處理,這裏可以刪除
String area_name = data.getAREA_NAME();
String month_desc = data.getMONTH_DESC();
//先刪除表裏已經存在的,再保存新的
mrePortDao.deleteTableData(tableName,timeType," AREA_NAME='"+area_name+"' AND MONTH_DESC='"+month_desc+"' ");
//-------------------end-----------------------
list.add(data);
// 達到BATCH_COUNT了,需要去存儲一次數據庫,防止數據幾萬條數據在內存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存儲完成清理 list
list.clear();
}
}
/**
* 所有數據解析完成了 都會來調用
* @param analysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 這裏也要保存數據,確保最後遺留的數據也存儲到數據庫
saveData();
LOGGER.info("所有數據解析完成!");
}
/**
* 加上存儲數據庫
*/
private void saveData() {
LOGGER.info("{}條數據,開始存儲數據庫!", list.size());
mrePortDao.saveRoomMonitorCoverData(list,timeType);
LOGGER.info("存儲數據庫成功!");
}
}
5、dao層
因爲我們這裏還是用的springJPA,所以大家可以看到上面我處理excel中的數據,要對原來數據庫中的數據進行刪除的時候,還是用拼接的那種格式,這裏大家要結合自己的框架進行修改,相信作爲技術人,你們改改還是可以的,如果有遇到不會改的,可以關注我的公衆號:zygxsq,公衆號裏有我的微信方式,可以聯繫我
demo
/**
* Created by yjl on 2019/12/20.
*/
@Component
public class MrePortDaoImpl implements MrePortDao {
@PersistenceContext
private EntityManager em;
@Autowired
protected JdbcTemplate jdbcTemplate;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Transactional
@Override
public Integer saveRoomMonitorCoverData(List<RoomMonitorCoverDataBean> params, String timeType) {
String tableName = "xxxxxxx";
StringBuilder sql = new StringBuilder();
sql.append("INSERT INTO ")
.append(tableName)
.append("(")
.append("TIME_ID,") //時間
.append("PROV_NAME,") //省
.append("AREA_NAME,") //地市
.append("MONTH_DESC,") //月份
.append(")VALUES(")
.append("?,?,?,?")
.append(")")
;
int[] len = jdbcTemplate.batchUpdate(sql.toString(), new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
RoomMonitorCoverDataBean pojo = params.get(i);
String month_desc = pojo.getMONTH_DESC();
String time_id = month_desc.replaceAll("\\D","")+"000000";
ps.setString(1,time_id); //時間
ps.setString(2,pojo.getPROV_NAME()); //省
ps.setString(3,pojo.getAREA_NAME()); //地市
ps.setString(4,month_desc); //月份
}
@Override
public int getBatchSize() {
return params.size();
}
});
return len.length;
}
@Transactional
@Modifying
@Query
@Override
public Integer deleteTableData(String tableName, String timeType, String whereInfo) {
String sql="DELETE FROM "+tableName+" WHERE 1=1 AND "+whereInfo;
logger.info("刪除表"+tableName+"裏"+whereInfo+"的數據sql:"+sql);
int deleteCnt = em.createNativeQuery(sql).executeUpdate();
logger.info("成功刪除"+tableName+"表中"+whereInfo+"的"+deleteCnt+"條數據");
return deleteCnt;
}
}
二、導出excel代碼示例
待更新