前言
Excel表單導入,是將Excel文件中的數據導入到數據庫;而Excel表單導出,是將數據庫的數據導出到Excel文件中。
在本文中開發的功能是基於SpringBoot + JPA框架進行開發,而在系統中,數據庫表結構又分成了兩種情況,一種是單表結構,另一種是存在@OneToMany
註解的主外鍵關聯的主輔表結構,所以本次開發目的是可以同時兼容兩種情況。
另外,因爲不同的數據表對應的Excel表單的數據和格式都不同,但是爲每一個數據表定製化開發Excel導入導出,工作重複度高,所以本次開發另一個目的是能夠做到所有數據表通用Excel表單導入導出。
起步
SpringBoot和JPA的環境請讀者自行準備,在這裏引入poi依賴來協助開發,版本選擇4.0.1。
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
開始
設計思路
功能設計
導入和導出功能都能通過傳入的服務參數,完成對指定表結構的導入或導出。
如:http://127.0.0.1/excel/{SERVER_NAME}/import (導入)
http://127.0.0.1/excel/{SERVER_NAME}/export (導出)
一、Excel導出
1、導出目標
可以導出兩種形式的Excel文件:
(1)數據模版,用於數據填入後進行Excel導入功能,命名規則爲:Excel-‘name’-Template.xlsx(如:Excel-Project-Template.xlsx);
(2)指定時間區間的數據,命名規則爲:Excel-‘name’.xlsx(如:Excel-Project.xlsx)。
2、導出格式
將數據表的字段以字段名+‘-’的格式(如:id-),依次插入到表單的首行單元格。如果導出的是指定區間的數據,則會在首行行尾追加’createTime’和‘createBy’兩個字段,分別表示數據創建時間和數據創建人(關聯關係表結構中,只在主表中追加),再將數據逐行插入。
*注:表頭字段的設計(如:id-),是爲了方便用戶在填入自己需要導入數據的過程中,可以在表頭字段後加上自己的註解(如:id-編號),並且不影響Excel表單的數據導入,數據填入過程中,請保留’-'符號。
3、導出結構
單表結構:在Entity字段定義中,不存在@OneToMany註解,在該結構下,只有一張Excel工作簿。
關聯關係表結構:在Entity字段定義中,存在@OneToMany註解,在該結構下,需要新建Excel工作薄,再將字段或數據填入新的工作簿中。
二、Excel導入
1、導入規則
將數據表的字段以字段名+‘-’的格式(如:id-),依次插入到表單的首行單元格,再將數據逐行填入字段對應的同列單元格即可。表頭字段名後可以加上自己的註解(如:id-編號),不影響表單的正常導入。
*注:爲避免用戶由於Excel表單格式的問題,數據導入失敗,請用戶先使用Excel導出功能,導出一份表單模板,再將數據填入模板後進行導入操作,保證數據可正常導入。
*注:若導入的數據爲關聯關係表結構,則輔表工作簿中的關聯字段需要和主表工作薄中的主鍵保持一致。
2、導入操作
根據@Id註解和@GeneratedValue註解判斷主鍵的類型,對主鍵做特殊處理,若是自增主鍵,會將主鍵置爲空,保證數據只做插入操作,不做更新操作;而若是非自增主鍵,不做處理,數據可以進行插入操作,或者對數據庫已經存在該主鍵的數據進行更新操作。
3、導入反饋
在數據轉化後,會將數據逐條存入數據庫,而在這個過程中,可能會存在數據的錯誤或者別的問題導致部分數據的存儲失敗,但是部分數據的存儲失敗不會導致整個Excel表單數據存儲的失敗回滾,而是將存儲失敗的數據對應的Excel表單單元行和失敗原因反饋給用戶,如:導入數據總數,成功導入數據總數,失敗導入數據總數以及失敗導入原因。
服務設計
- 內部服務設計
(1)RIA報表服務抽象接口
/**
* RIA報表服務抽象接口
* @author xinyao.zhang
*/
public interface BaseRiaExcelService {
/**
* 導入Excel文件數據
* @param file 文件
* @return JSONObject
*/
default JSONObject importExcelData(MultipartFile file){return null;}
/**
* Excel文件 轉List數據
* @param file
* @return
*/
default List excelDataConvert(MultipartFile file){return null;}
/**
* 導出Excel文件數據
* @param flag 0:導出Excel格式 1:導出指定時間區間數據
* @param beginTime 開始時間
* @param endTime 結束時間
* @param response 返回數據
*/
default void exportExcelData(Integer flag, Long beginTime, Long endTime, HttpServletResponse response){}
/**
* 數據構建Excel文件
* @param flag
* @param beginTime
* @param endTime
*/
default XSSFWorkbook dataToExcel(Integer flag, Long beginTime, Long endTime){return null;}
}
(2)RIA基礎服務類
/**
* RIA基礎服務類
* @author xinyao.zhang
*/
public interface BaseRiaService extends BaseService,BaseRiaExcelService{
}
(3)基礎服務接口
/**
* 基礎服務接口
* @author xinyao.zhang
*/
public interface BaseService<T, ID extends Serializable> {
/**
* 查找單個實例
* @param id 實例ID
* @return T
*/
T find(ID id);
}
(4)基礎服務邏輯層
/**
* 基礎服務邏輯層
* @author xinyao.zhang
*/
public class BaseServiceImpl<T, ID extends Serializable> implements BaseService<T, ID> {
private final Logger logger = LoggerFactory.getLogger(getClass());
protected Class<T> entityClass = (Class<T>) GenericClassUtils.getSuperClassGenericType(getClass(), 0);
protected Class<ID> idClass = (Class<ID>) GenericClassUtils.getSuperClassGenericType(getClass(), 1);
protected BaseJpaRepository<T, ID> baseJpaRepository;
public BaseServiceImpl(BaseJpaRepository baseJpaRepository){
this.baseJpaRepository = baseJpaRepository;
}
@Override
public T find(ID id){
T optional = null;
//ID類型轉換
if(idClass.isInstance(id)){
optional = baseJpaRepository.findOne(idClass.cast(id));
}else{
switch (idClass.getName()) {
case "java.lang.String":
optional = baseJpaRepository.findOne(idClass.cast(String.valueOf(id)));
break;
case "java.lang.Integer":
optional = baseJpaRepository.findOne(idClass.cast(Integer.valueOf(id.toString())));
break;
case "java.lang.Long":
optional = baseJpaRepository.findOne(idClass.cast(Long.valueOf(id.toString())));
break;
default:
break;
}
}
if(null != optional){
return optional;
}
return null;
}
}
(5)泛型工具類
/**
* 泛型工具類
* @author xinyao.zhang
*/
@SuppressWarnings("rawtypes")
public class GenericClassUtils {
public static Class getSuperClassGenericType(Class clazz, int index) {
Type genType = clazz.getGenericSuperclass();
if (!(genType instanceof ParameterizedType)) {
return null;
}
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
if (index >= params.length || index < 0) {
return null;
}
if (!(params[index] instanceof Class)) {
return null;
}
return (Class) params[index];
}
}
(6)RIA基礎實現基類
/**
* RIA基礎實現基類
* @author xinyao.zhang
* @param <T>
* @param <ID>
*/
@SuppressWarnings("unchecked")
public abstract class BaseRiaServiceImpl<T, ID> extends BaseServiceImpl implements BaseRiaService {
private static final Logger logger = LoggerFactory.getLogger(BaseRiaServiceImpl.class);
private BaseRiaExcel<T,ID> tBaseRiaExcel;
public BaseRiaServiceImpl(BaseJpaRepository baseJpaRepository) {
super(baseJpaRepository);
tBaseRiaExcel = new BaseRiaExcel(entityClass,idClass,baseJpaRepository);
}
protected BaseRiaExcel baseRiaExcel() {
return tBaseRiaExcel;
}
@Override
public JSONObject importExcelData(MultipartFile file) {
return tBaseRiaExcel.importExcelData(excelDataConvert(file));
}
@Override
public List excelDataConvert(MultipartFile file) {
return tBaseRiaExcel.excelDataConvert(file);
}
@Override
public void exportExcelData(Integer flag, Long beginTime, Long endTime, HttpServletResponse response) {
tBaseRiaExcel.exportExcelData(dataToExcel(flag, beginTime, endTime), flag, response);
}
@Override
public XSSFWorkbook dataToExcel(Integer flag, Long beginTime, Long endTime) {
return tBaseRiaExcel.dataToExcel(flag, beginTime, endTime);
}
}
(7)RIA報表服務類
/**
* RIA報表服務類
* @author xinyao.zhang
* @param <T>
* @param <ID>
*/
public class BaseRiaExcel<T,ID> extends BaseServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(BaseRiaExcel.class);
private Class<T> entityClass;
public BaseRiaExcel(Class<T> entityClass,Class<ID> idClass,BaseJpaRepository baseJpaRepository){
super(baseJpaRepository);
super.entityClass = entityClass;
super.idClass = idClass;
this.entityClass = entityClass;
}
/**
* List數據導入數據庫
* @param list
* @return
*/
public JSONObject importExcelData(List<T> list) {
JSONObject result = new JSONObject();
try{
JSONArray error = null;
int successCount = 0;
int failCount = 0;
for(int index = 0; index < list.size(); index++) {
int rowNum = index + 2;
//保存數據
try{
T entity = list.get(index);
boolean isUpdate = false;
Field[] fields = entityClass.getDeclaredFields();
for(Field f: fields){
//判斷id主鍵是否置空
Id id = f.getAnnotation(Id.class);
if(null != id){
f.setAccessible(true);
Object idObject = f.get(entity);
if(null != idObject){
String idValue = String.valueOf(idObject);
T oldEntity = (T)find(idValue);
//id主鍵沒有置空且存在舊數據,進行更新操作
if(null != oldEntity){
BeanCopyUtils.copyPropertiesIgnoreNull(oldEntity, entity);
baseJpaRepository.saveAndFlush(oldEntity);
isUpdate = true;
}
}
break;
}
}
if(!isUpdate){
baseJpaRepository.saveAndFlush(entity);
}
logger.info("單元行:{},數據保存成功", rowNum);
successCount++;
}catch (Exception e){
if(null == error){
error = new JSONArray();
}
logger.error("單元行:{},數據保存失敗", rowNum, e);
error.add("單元行:" + rowNum + ",數據保存失敗," + e.getMessage());
failCount++;
}
}
result.put("rowsCount", list.size());
result.put("successCount", successCount);
result.put("failCount", failCount);
if(null != error){
result.put("error", error);
}
} catch (Exception e){
throw new BusinessException("Excel數據導入失敗");
}
return result;
}
/**
* Excel文件數據轉List
* @param file
* @return
*/
public List<T> excelDataConvert(MultipartFile file){
List<T> list;
try{
XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file.getInputStream());
int sheetNumber = xssfWorkbook.getNumberOfSheets();
if(sheetNumber > 1){
list = multipleSheetDataConvert(xssfWorkbook);
}else {
list = singleSheetDataConvert(xssfWorkbook);
}
} catch (Exception e){
throw new BusinessException("Excel數據導入失敗");
}
return list;
}
/**
* 單工作薄數據處理
* @param xssfWorkbook
* @return
*/
private List<T> singleSheetDataConvert(XSSFWorkbook xssfWorkbook){
List<T> list = new ArrayList<>();
//默認:第一頁工作薄
XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0);
//首行頭信息獲取
XSSFRow firstRow = xssfSheet.getRow(0);
for(int rowNum = 1; rowNum <= xssfSheet.getLastRowNum(); rowNum++) {
//行數據獲取
XSSFRow dataRow = xssfSheet.getRow(rowNum);
try{
T entity = convertToEntity(firstRow, dataRow);
list.add(entity);
}catch (Exception e){
logger.error("Excel單元行:{},轉化Bean失敗", rowNum + 1, e);
}
}
return list;
}
/**
* 數據行轉化Bean
* @param firstRow 首行頭信息
* @param dateRow 行數據
* @return
*/
private T convertToEntity(XSSFRow firstRow, XSSFRow dateRow){
T entity;
try{
JSONObject jsonObject = new JSONObject();
for(int cellNum = 0; cellNum < dateRow.getLastCellNum(); cellNum++){
//獲取單元格數據
XSSFCell cell = dateRow.getCell(cellNum);
String cellValue = getCellValue(cell);
if(null == cellValue){
continue;
}
//獲取頭信息屬性
XSSFCell headCell = firstRow.getCell(cellNum);
String headCellValue = getCellValue(headCell);
String field = headCellValue.split("-")[0];
jsonObject.put(field, cellValue);
//去除自增主鍵
Id id = entityClass.getDeclaredField(field).getAnnotation(Id.class);
if(null != id){
//自增主鍵判斷
GeneratedValue generatedValue = entityClass.getDeclaredField(field).getAnnotation(GeneratedValue.class);
if(null != generatedValue && GenerationType.SEQUENCE.equals(generatedValue.strategy())){
jsonObject.remove(field);
}
}
}
entity = JSONObject.parseObject(jsonObject.toJSONString(), entityClass);
return entity;
}catch (Exception e){
logger.error("Excel單元行轉化Bean失敗,{}", dateRow, e);
return null;
}
}
/**
* 多工作薄,存在關聯數據
* 轉化爲主表Bean List數據
* @param xssfWorkbook
* @return
*/
private List<T> multipleSheetDataConvert(XSSFWorkbook xssfWorkbook){
try{
List<T> list = new ArrayList<>();
Field[] fields = entityClass.getDeclaredFields();
Field idField = null;
//存儲關聯數據
Map<String, Map> map = new HashMap<>();
//存儲關聯表的關聯字段
Map<String, String> columnMap = new HashMap<>();
//存儲關聯表的類
Map<String, Class> classMap = new HashMap<>();
//存儲關聯表在主表中的屬性
Map<String, Field> fieldMap = new HashMap<>();
//存儲關聯表中的自增id
Map<String, String> idMap = new HashMap<>();
//遍歷主表屬性
for(Field field: fields){
Id id = field.getAnnotation(Id.class);
if(null != id){
idField = field;
continue;
}
//判斷是否一對多映射
OneToMany oneToMany = field.getAnnotation(OneToMany.class);
if(null != oneToMany){
JoinColumn joinColumn = field.getAnnotation(JoinColumn.class);
if(null != joinColumn){
String fieldName = underlineToCamel(joinColumn.name());
Class clazz = oneToMany.targetEntity();
String entityName = clazz.getSimpleName();
//實例化map存儲關聯數據
Map<String, Collection> entityMap = new HashMap<>();
map.put(entityName, entityMap);
columnMap.put(entityName, fieldName);
classMap.put(entityName, clazz);
fieldMap.put(entityName, field);
Field[] entityFields = clazz.getDeclaredFields();
for(Field f: entityFields){
Id entityId = f.getAnnotation(Id.class);
if(null != entityId){
//自增主鍵判斷
GeneratedValue generatedValue = f.getAnnotation(GeneratedValue.class);
if(null != generatedValue && GenerationType.SEQUENCE.equals(generatedValue.strategy())){
idMap.put(entityName, f.getName());
}
break;
}
}
}
}
}
if(null == idField){
logger.error("Bean沒有Id註解屬性");
throw new BusinessException("Bean沒有Id註解屬性");
}
//遍歷工作簿
for(int sheetNum = 0; sheetNum < xssfWorkbook.getNumberOfSheets(); sheetNum++){
XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(sheetNum);
String sheetName = xssfSheet.getSheetName();
//首行頭信息獲取
XSSFRow firstRow = xssfSheet.getRow(0);
//行數據獲取
for(int rowNum = 1; rowNum <= xssfSheet.getLastRowNum(); rowNum++){
XSSFRow xssfRow = xssfSheet.getRow(rowNum);
JSONObject jsonObject = new JSONObject();
String relationId = null;
//單元格數據獲取
for(int cellNum = 0; cellNum < xssfRow.getLastCellNum(); cellNum++){
//獲取單元格數據
XSSFCell cell = xssfRow.getCell(cellNum);
String cellValue = getCellValue(cell);
if(null == cellValue){
continue;
}
//獲取頭信息屬性
XSSFCell headCell = firstRow.getCell(cellNum);
String headCellValue = getCellValue(headCell);
String field = headCellValue.split("-")[0];
jsonObject.put(field, cellValue);
//判斷是否是關聯表中關聯字段
if(columnMap.containsKey(sheetName)){
String column = columnMap.get(sheetName);
if(field.equals(column)){
relationId = cellValue;
}
}
}
try{
//關聯數據處理
if(map.containsKey(sheetName) && classMap.containsKey(sheetName) && null != relationId){
Map<String, Collection> entityMap = map.get(sheetName);
if(null == entityMap.get(relationId)){
Field columnField = fieldMap.get(sheetName);
Collection collection = getFieldCollection(columnField);
entityMap.put(relationId, collection);
}
//去除自增主鍵
if(idMap.containsKey(sheetName)){
String id = idMap.get(sheetName);
jsonObject.put(id, null);
}
//去除關聯字段
String column = columnMap.get(sheetName);
jsonObject.put(column, null);
entityMap.get(relationId).add(JSONObject.parseObject(jsonObject.toJSONString(), classMap.get(sheetName)));
}else {
T entity = JSONObject.parseObject(jsonObject.toJSONString(), entityClass);
list.add(entity);
}
}catch (Exception e){
logger.error("Excel單元行:{},轉化Bean失敗", rowNum + 1, e);
}
}
}
boolean idIsSequence = false;
GeneratedValue generatedValue = idField.getAnnotation(GeneratedValue.class);
if(null != generatedValue && GenerationType.SEQUENCE.equals(generatedValue.strategy())){
idIsSequence = true;
}
//設置關聯數據
for(int index = 0; index < list.size(); index++){
int num = index + 2;
try{
T t = list.get(index);
idField.setAccessible(true);
String id = String.valueOf(idField.get(t));
//遍歷關聯屬性
for(Map.Entry<String, Field> entry: fieldMap.entrySet()){
String entityName = entry.getKey();
Field field = entry.getValue();
Map<String, Collection> entityMap = map.get(entityName);
Collection entity = entityMap.get(id);
field.setAccessible(true);
field.set(t, entity);
}
//去除自增主鍵
if(idIsSequence){
idField.set(t, null);
}
}catch (Exception e){
logger.error("Excel單元行:{},轉化Bean失敗", num, e);
}
}
return list;
}catch (Exception e){
logger.error("Excel數據行轉化Bean失敗", e);
throw new BusinessException("Excel數據行轉化Bean失敗");
}
}
/**
* 下劃線格式字符串轉換爲駝峯格式字符串
* @param param
* @return
*/
private static String underlineToCamel(String param) {
if (param == null || "".equals(param.trim())) {
return "";
}
int len = param.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = param.charAt(i);
if (c == '_') {
if (++i < len) {
sb.append(Character.toUpperCase(param.charAt(i)));
}
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* 獲取關聯屬性的存儲集合
* @param field
* @return
*/
private Collection getFieldCollection(Field field){
if(null == field){
return null;
}
Collection collection = null;
Class classType = field.getType();
if(classType.isAssignableFrom(List.class)){
collection = new ArrayList();
} else if(classType.isAssignableFrom(Set.class)){
collection = new ArrayList();
}
return collection;
}
/**
* 獲取單元格數據
* @param cell
* @return
*/
private String getCellValue(XSSFCell cell){
if(null == cell){
return null;
}
String result = null;
switch (cell.getCellType()){
case NUMERIC:
if(DateUtil.isCellDateFormatted(cell)){
Date createDate = cell.getDateCellValue();
result = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(createDate);
}else {
cell.setCellType(CellType.STRING);
result = cell.getStringCellValue();
}
break;
case FORMULA:
cell.setCellType(CellType.STRING);
result = cell.getStringCellValue();
break;
case STRING:
cell.setCellType(CellType.STRING);
result = cell.getStringCellValue();
break;
case BOOLEAN:
cell.setCellType(CellType.BOOLEAN);
result = String.valueOf(cell.getBooleanCellValue());
break;
case _NONE:
break;
case BLANK:
break;
case ERROR:
break;
default:
break;
}
return result;
}
/**
* 導出Excel文件
* @param xssfWorkbook
* @param flag
* @param response
*/
public void exportExcelData(XSSFWorkbook xssfWorkbook, Integer flag, HttpServletResponse response) {
if(null == xssfWorkbook){
return;
}
OutputStream out = null;
try{
String name = entityClass.getSimpleName();
if(flag.equals(0)){
name += "-Template";
}
//設置response信息
String title = "Excel-" + name + ".xlsx";
String headStr = "attachment; filename=\"" + new String(title.getBytes("gb2312"), "UTF-8" ) + "\"";
response.setContentType("octets/stream");
response.setContentType("APPLICATION/OCTET-STREAM");
response.setHeader("Content-Disposition", headStr);
//導出Excel
out = response.getOutputStream();
xssfWorkbook.write(out);
}catch (Exception e){
logger.error("Excel文件導出失敗", e);
}finally {
if(null != out){
try {
out.close();
}catch (Exception e){
logger.error("OutputStream流關閉失敗", e);
}
}
}
}
/**
* 數據構建Excel文件
* @param flag
* @param beginTime
* @param endTime
* @return
*/
public XSSFWorkbook dataToExcel(Integer flag, Long beginTime, Long endTime){
if(!flag.equals(0) && !flag.equals(1)){
return null;
}
String name = entityClass.getSimpleName();
Field[] fields = entityClass.getDeclaredFields();
//構建Excel
XSSFWorkbook xssfWorkbook = new XSSFWorkbook();
XSSFSheet xssfSheet = xssfWorkbook.createSheet(name);
xssfSheet.setDefaultColumnWidth(20);
//設置單元格樣式
XSSFCellStyle style = xssfWorkbook.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
XSSFFont font = xssfWorkbook.createFont();
font.setBold(true);
font.setFontHeight(12);
style.setFont(font);
Map<String, String> entityNamesMap = new HashMap<>();
Map<String, Field[]> entityFieldsMap = new HashMap<>();
//首行插入字段
XSSFRow firstRow = xssfSheet.createRow(0);
for(int i = 0; i < fields.length; i++){
Field field = fields[i];
//判斷是否一對多映射
OneToMany oneToMany = field.getAnnotation(OneToMany.class);
if(null != oneToMany){
Class clazz = oneToMany.targetEntity();
//新建工作簿
String entityName = clazz.getSimpleName();
XSSFSheet entitySheet = xssfWorkbook.createSheet(entityName);
entitySheet.setDefaultColumnWidth(20);
Field[] entityFields = clazz.getDeclaredFields();
entityFieldsMap.put(field.getName(), entityFields);
entityNamesMap.put(field.getName(), entityName);
//首行插入字段
XSSFRow entityFirstRow = entitySheet.createRow(0);
for(int cellNum = 0; cellNum < entityFields.length; cellNum++){
XSSFCell cell = entityFirstRow.createCell(cellNum);
cell.setCellValue(entityFields[cellNum].getName() + "-");
cell.setCellStyle(style);
}
continue;
}
XSSFCell xssfCell = firstRow.createCell(i);
xssfCell.setCellValue(field.getName() + "-");
xssfCell.setCellStyle(style);
}
//flag:1
if(flag.equals(1)){
//新增createTime字段單元格
XSSFCell createTimeCell = firstRow.createCell(firstRow.getLastCellNum());
createTimeCell.setCellValue("createTime-");
createTimeCell.setCellStyle(style);
//新增createBy字段單元格
XSSFCell createByCell = firstRow.createCell(firstRow.getLastCellNum());
createByCell.setCellValue("createBy-");
createByCell.setCellStyle(style);
//獲取時間區間數據
List<T> list = baseJpaRepository.findAll((root,query,cb) -> {
List<Predicate> predicateList = new ArrayList<>();
predicateList.add(cb.between(root.get("createTime"), new Date(beginTime), new Date(endTime)));
Predicate[] array = new Predicate[predicateList.size()];
return cb.and(predicateList.toArray(array));
});
font.setBold(false);
style.setFont(font);
int dataIndex = 1;
for (T e : list) {
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(e));
//構建新的單元行
XSSFRow xssfRow = xssfSheet.createRow(dataIndex++);
//插入常規數據
for (int cellNum = 0; cellNum < fields.length; cellNum++) {
Field field = fields[cellNum];
String fieldName = field.getName();
//判斷是否爲關聯屬性
if(entityFieldsMap.containsKey(fieldName) && entityNamesMap.containsKey(fieldName)){
//關聯工作薄處理數據
XSSFSheet entitySheet = xssfWorkbook.getSheet(entityNamesMap.get(fieldName));
JSONArray entityArray = JSONArray.parseArray(String.valueOf(jsonObject.get(fieldName)));
entityArray.forEach(entity -> {
JSONObject entityJson = JSONObject.parseObject(JSON.toJSONString(entity));
//構建新的單元行
XSSFRow entityRow = entitySheet.createRow(entitySheet.getLastRowNum() + 1);
Field[] entityFields = entityFieldsMap.get(fieldName);
for(int entityCellNum = 0; entityCellNum < entityFields.length; entityCellNum++){
Field entityField = entityFields[entityCellNum];
String entityFieldName = entityField.getName();
XSSFCell cell = entityRow.createCell(entityCellNum);
cell.setCellValue(getFieldData(entityField, entityJson.get(entityFieldName)));
cell.setCellStyle(style);
}
});
continue;
}
XSSFCell xssfCell = xssfRow.createCell(cellNum);
xssfCell.setCellValue(getFieldData(field, jsonObject.get(fieldName)));
xssfCell.setCellStyle(style);
}
//插入createTime數據
XSSFCell createTimeDataCell = xssfRow.createCell(xssfRow.getLastCellNum());
createTimeDataCell.setCellValue(DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.CHINA).format(jsonObject.get("createTime")));
createTimeDataCell.setCellStyle(style);
//插入createBy數據
XSSFCell createByDataCell = xssfRow.createCell(xssfRow.getLastCellNum());
createByDataCell.setCellValue(String.valueOf(jsonObject.get("createBy")));
createByDataCell.setCellStyle(style);
}
}
return xssfWorkbook;
}
/**
* 轉化屬性值爲String
* @param field
* @param object
* @return
*/
protected String getFieldData(Field field, Object object){
if(null == field || null == object){
return null;
}
String result;
String type = field.getGenericType().toString();
switch (type){
case "class java.util.Date":
result = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.CHINA).format(object);
break;
default:
result = String.valueOf(object);
break;
}
return result;
}
}
- 外部接口設計
(1)Excel服務接口
/**
* Excel服務接口
* @author xinyao.zhang
*/
public interface RiaExcelService {
/**
* 導入Excel文件數據
* @param serviceName 服務名
* @param file 文件
* @return JSONObject
*/
JSONObject importExcelData(String serviceName,MultipartFile file);
/**
* 導出Excel文件數據
* @param serviceName 服務名
* @param exportExcelDto 導出參數
*/
void exportExcelData(String serviceName, ExportExcelDto exportExcelDto);
}
(2)Excel服務實現類
/**
* Excel服務實現類
* @author chenjianian
*/
@Service
public class RiaExcelServiceImpl extends RiaServiceImpl implements RiaExcelService {
@Override
public JSONObject importExcelData(String serviceName,MultipartFile file) {
BaseRiaService baseRiaService = super.riaBaseService(serviceName);
return baseRiaService.importExcelData(file);
}
@Override
public void exportExcelData(String serviceName, ExportExcelDto exportExcelDto) {
BaseRiaService baseRiaService = super.riaBaseService(serviceName);
baseRiaService.exportExcelData(exportExcelDto.getFlag()
,exportExcelDto.getBeginTime(),exportExcelDto.getEndTime(),exportExcelDto.getResponse());
}
}
(3)RIA服務實現類
/**
* RIA服務實現類
* @author xinyao.zhang
*/
public class RiaServiceImpl{
/**
* 獲取流程對應服務
* @param riaServiceName 流程服務名
* @return RiaBaseService
*/
protected BaseRiaService riaBaseService(String riaServiceName){
ApplicationContext applicationContext = ApplicationContextRegister.getApplicationContext();
return (BaseRiaService) applicationContext.getBean(riaServiceName);
}
}
(4)應用上下文配置
/**
* 應用上下文配置
* @author xinyao.zhang
*/
@Component
@Lazy(false)
public class ApplicationContextRegister implements ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationContextRegister.class);
private static ApplicationContext APPLICATION_CONTEXT;
/**
* 設置spring上下文 * * @param applicationContext spring上下文 * @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
LOGGER.debug("ApplicationContext registed-->{}", applicationContext);
APPLICATION_CONTEXT = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return APPLICATION_CONTEXT;
}
}
(5)表單-控制層
/**
* 表單-控制層
* @author chenjianian
*/
@RestController
@RequestMapping("excel")
public class ExcelModelController {
@Resource
private RiaExcelService riaExcelService;
/**
* 導入Excel文件數據
* @param file
* @return ResultDataDto
*/
@ApiOperation(value = "導入Excel數據", notes = "根據導入的Excel文件導入數據到數據庫")
@RequestMapping(value = "/{MODEL_NAME}/import", method = RequestMethod.POST)
@ResponseBody
public ResultDataDto importExcelData(@PathVariable("MODEL_NAME") String serviceName,@RequestParam("file") MultipartFile file){
String fileName = file.getOriginalFilename();
if(!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")){
throw new BusinessException(CommonConstants.EXCEPTION_STATUS, "非法文件格式");
}
try {
JSONObject result = riaExcelService.importExcelData(serviceName,file);
ResultDataDto resultDataDto = new ResultDataDto();
resultDataDto.setCode(CommonConstants.SUCCESS_CODE);
resultDataDto.setMessage("數據導入完成");
resultDataDto.setStatus(CommonConstants.SUCCESS_STATUS);
resultDataDto.setData(result);
return resultDataDto;
} catch (Exception e){
throw new BusinessException(CommonConstants.EXCEPTION_STATUS, CommonConstants.EXCEPTION_MESSAGE + e.getMessage(), e);
}
}
/**
* 導出Excel文件數據
* @param serviceName 模塊名
* @param exportExcelDto 導出參數
*/
@ApiOperation(value = "導出Excel數據", notes = "將數據庫數據導出到Excel文件")
@RequestMapping(value = "/{MODEL_NAME}/export", method = RequestMethod.POST)
@ResponseBody
public void exportExcelData(@PathVariable("MODEL_NAME") String serviceName, @RequestBody @Valid ExportExcelDto exportExcelDto, HttpServletResponse response){
try {
exportExcelDto.setResponse(response);
riaExcelService.exportExcelData(serviceName,exportExcelDto);
} catch (Exception e){
throw new BusinessException(CommonConstants.EXCEPTION_STATUS, CommonConstants.EXCEPTION_MESSAGE + e.getMessage(), e);
}
}
}
- 業務Service層設計
在業務Service層,只需繼承BaseRiaServiceImpl類,即擁有了通用的Excel功能,而接口SERVICE_NAME只需傳入服務的名字及可完成對應Excel功能的調用。在此引入一個例子,如:職員-服務EmployeeServiceImpl。
/**
* 職員-服務實現層
*
* @author xinyao.zhang
*/
@Service("employee")
public class EmployeeServiceImpl extends BaseRiaServiceImpl<Employee, Long> {
}
*注:可重寫RIA報表服務抽象接口BaseRiaExcelService,在通用的基礎上完成定製化的導入導出開發。
演示
職員Employee
一、導出數據模版
二、填入數據後導入
數據導出部分在這裏不做演示。
總結
本次的開發,實現了對單表結構和存在主外鍵關聯的主輔表結構的通用Excel導入和導出功能,用戶可以選擇參數的傳入控制相應數據表的導入和導出。
在功能支持上,自增主鍵表結構,僅支持對應數據的插入操作,不支持對數據的更新操作;非自增主鍵表結構,支持對應數據的插入操作,也支持對數據的更新操作。