Excel導入在實際的開發(fā)中,經(jīng)常會用到鹅经,可是我們往往需要研究一番這個Excel表格的結(jié)構(gòu)才能夠著手寫Excel導入的代碼,而這份代碼有不能夠復用怎诫,常常導致一表一碼的情況瘾晃,為了提高開發(fā)效率,因此寫了些代碼和解決思路
大前提
每一份Excel表格都有唯一標示某列或某行業(yè)務含義的標示幻妓,例如列名蹦误,某單元格特定字符。
很明顯根據(jù)某些特定標示肉津,我是能夠知道哪個單元格是我想要的數(shù)據(jù)
解決思路
1.如何讓程序根據(jù)某一字段標識與導入實體類字段存在對應關(guān)系强胰,找出數(shù)據(jù)所在位置?
例如給出如下對應關(guān)系(參照上圖教學進度的妹沙,給出如下配置)
List<ImportField> fields = new ArrayList<>();
//公用字段
fields.add(new ImportField("課程名稱","name"));
fields.add(new ImportField("專業(yè)","major"));
fields.add(new ImportField("年級","grade"));
fields.add(new ImportField("班級","classes"));
//多值字段
fields.add(new ImportField("授課方式","type",true,1,0));
fields.add(new ImportField("授課教師","teacher",true,1,0));
fields.add(new ImportField("授課內(nèi)容","content",true,1,0));
fields.add(new ImportField("上課地點","address",true,1,0));
fields.add(new ImportField("周次","week",true,1,0));
fields.add(new ImportField("星期","day",true,1,0));
fields.add(new ImportField("節(jié)次","scope",true,1,0));
2.如何通過給定的映射關(guān)系遍歷Sheet和得到某一標識所在行和列偶洋?
首先我們需要定義一個導入配置,然后程序就是根據(jù)這一個導入配置去取數(shù)據(jù)
例如給出如下實體類
public class ImportField {
//別名
private String alias;
//數(shù)據(jù)庫字段名稱或者實體類名稱
private String name;
//是否多個
private boolean isMulti = false;
//行偏移量
private int xOffset=0;
//列偏移量
private int yOffset=0;
//起始行
private int row;
//起始列
private int col;
//多值屬性記錄數(shù)
private int multiCount = 0;
//是否分析得到起始行列位置
private boolean isComplete = false;
public ImportField() {
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public boolean isMulti() {
return isMulti;
}
public void setMulti(boolean multi) {
isMulti = multi;
}
public int getxOffset() {
return xOffset;
}
public void setxOffset(int xOffset) {
this.xOffset = xOffset;
}
public int getyOffset() {
return yOffset;
}
public void setyOffset(int yOffset) {
this.yOffset = yOffset;
}
//計算所在列是需要加上行偏移量
public int getRow() {
return row+xOffset;
}
public void setRow(int row) {
this.row = row;
}
//計算所在列是需要加上列偏移量
public int getCol() {
return col+yOffset;
}
public void setCol(int col) {
this.col = col;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ImportField(String alias, String name, boolean isMulti, int xOffset, int yOffset) {
this.alias = alias;
this.name = name;
this.isMulti = isMulti;
this.xOffset = xOffset;
this.yOffset = yOffset;
}
public ImportField(String alias, String name) {
this.alias = alias;
this.name = name;
}
public void setPosition(int row, int col){
this.col = col;
this.row = row;
}
public boolean isComplete() {
return isComplete;
}
public void setComplete(boolean complete) {
isComplete = complete;
}
public void getNextRow(){
this.row ++;
}
public void addMultiCount(){
this.multiCount++;
}
public int getMultiCount() {
return multiCount;
}
遍歷Excel得到標識位置所在行列
//分析即將導入的文件
/**
* HashMap中包含
*List<ImportField> fields
*HSSFSheet
*/
private static HashMap<String,Object> analysisExcel(
InputStream inputStream, List<ImportField> fields)
throws IOException,FormatException{
HashMap<String,Object> analysisResult = new HashMap<>();
int progress = 0;//掃描進度
HSSFWorkbook hssfWorkbook = new HSSFWorkbook(inputStream);
HSSFSheet hssfSheet = hssfWorkbook.getSheetAt(0);//默認獲取第一個
if (hssfSheet == null) {
throw new FormatException("讀取模板文件為空距糖!");
}
for (int rowNum = 1; rowNum <= hssfSheet.getLastRowNum(); rowNum++) {
HSSFRow hssfRow = hssfSheet.getRow(rowNum);
if(hssfRow == null){
continue;
}
for(int colNum = 0; colNum<hssfRow.getPhysicalNumberOfCells(); colNum++){
HSSFCell cell = hssfRow.getCell(colNum);
if(cell != null && StringUtils.isNoneBlank(cell.getStringCellValue())){
String val = cell.getStringCellValue().trim();
for(ImportField field : fields){
if(val.indexOf(field.getAlias()) != -1){
field.setPosition(cell.getRowIndex(),cell.getColumnIndex());
field.setComplete(true);
progress++;
if(progress == fields.size()){
//如果所需字段的位置都找到了玄窝,提前結(jié)束循環(huán)
analysisResult.put(FIELDS,fields);
analysisResult.put(SHEET,hssfSheet);
return analysisResult;
}
}
}
}
}
}
//如果沒有提前跳出遍歷,那么就是證明某個標識在Excel中沒找到
String errorMsg = "";
for (ImportField field : fields){
if(!field.isComplete()){
errorMsg = errorMsg+" "+field.getAlias();
}
}
throw new FormatException("導入課程表格式錯誤悍引!不存在列"+errorMsg);
}
3.讀取數(shù)據(jù)恩脂,返回指定實體類集合
通過以上的遍歷,可以得到含有行列位置的導入配置和一個Sheet對象
//導入excel
private static List<Map<String,String>> importExcel(HashMap<String,Object> analysisResult)
throws FormatException{
List<ImportField> fields = (List<ImportField>) analysisResult.get(FIELDS);
HSSFSheet hssfSheet = (HSSFSheet) analysisResult.get(SHEET);
if(fields == null || hssfSheet == null){
throw new FormatException("分析導入文件異常");
}
//按行大小排序,因為是從第一行開始遍歷的讀取的趣斤,因此排序可以優(yōu)先讀取俩块,不用再倒回來讀取
fields.sort(new Comparator<ImportField>() {
@Override
public int compare(ImportField o1, ImportField o2) {
return o1.getRow()-o2.getRow();
}
});
//獲取公共屬性和多值屬性
HashMap<String,String> singleFields = new HashMap<>();
HashMap<String,List<String>> multiFields = new HashMap<>();
for (int rowNum = 1; rowNum <= hssfSheet.getLastRowNum(); rowNum++) {
HSSFRow hssfRow = hssfSheet.getRow(rowNum);
if(hssfRow == null){
continue;
}
for(int colNum = 0; colNum<hssfRow.getPhysicalNumberOfCells(); colNum++){
HSSFCell cell = hssfRow.getCell(colNum);
if(cell == null){
continue;
}
String val = cell.getStringCellValue();
for(ImportField field : fields){
if (field.getRow() == rowNum && field.getCol() == colNum){
if(!field.isMulti()){
singleFields.put(field.getName(),val);
}else{
//excel表格在A4紙出現(xiàn)第二頁的情況下處理如下
if(val.indexOf(field.getAlias()) == -1 && StringUtils.isNoneBlank(val)){
if(!multiFields.containsKey(field.getName())){
List<String> vals = new ArrayList<>();
vals.add(val);
multiFields.put(field.getName(),vals);
}else{
multiFields.get(field.getName()).add(val);
}
field.addMultiCount();
}
field.getNextRow();//跳出循環(huán)后讀取下一行
}
break;
}
}
}
}
//至此我們得到了2個HashMap
//公用屬性和多值屬性
//那么每一個多值屬性的數(shù)量應該是相等的,否則這份Excel表格的據(jù)就不合格的浓领。
int multiCount = 0;
String errorMsg ="";
String currentCol="";//選取的某一個多記錄屬性字段
for(ImportField field : fields){
if (field.isMulti()){
if(multiCount == 0){
currentCol = field.getAlias();
multiCount = field.getMultiCount();
}else {
if(field.getMultiCount() != multiCount){
errorMsg = errorMsg +","+field.getAlias()+"記錄數(shù)為:"+field.getMultiCount();
}
}
}
}
if(StringUtils.isNoneBlank(errorMsg)){
throw new FormatException("導入課程表列["+errorMsg.substring(1)+"]與["+currentCol+"]記錄數(shù):"+multiCount+"不一致");
}
//將公用屬性放入多值屬性
//例如
//公用字段是
//{“name”:"小明"}
//多種字段是
//{"phone":" [電話1玉凯,電話2,電話3]"}
//結(jié)合之后變成
//{[{“name”:"小明","phone":"[電話1]"},{“name”:"小明","phone":"[電話2]"},{“name”:"小明","phone":"[電話3]"}]}
for(ImportField field : fields){
if(!field.isMulti()){
List<String> vals = new ArrayList<>();
for(int i=0;i<multiCount;i++){
vals.add(singleFields.get(field.getName()));
}
multiFields.put(field.getName(),vals);
}
}
List<Map<String,String>> list = new ArrayList<>();
for(int i=0;i<multiCount;i++){
HashMap<String,String> data = new HashMap<>();
for(String key : multiFields.keySet()){
List<String> vals = multiFields.get(key);
data.put(key,vals.get(i));
}
list.add(data);
}
return list;
}
4.通過上面的讀取可以得到List<HashMap<String,String>>,下面的問題就是HashMap轉(zhuǎn)實體類對象的問題了镊逝,我推薦一下這種方式
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.5.1</version>
</dependency>
然后簡單封裝一下
public class BeanMapper {
private static DozerBeanMapper dozer = new DozerBeanMapper();
public BeanMapper() {
}
public static <T> T map(Object source, Class<T> destinationClass) {
return dozer.map(source, destinationClass);
}
public static <T> List<T> mapList(Collection sourceList, Class<T> destinationClass) {
ArrayList destinationList = Lists.newArrayList();
Iterator i$ = sourceList.iterator();
while(i$.hasNext()) {
Object sourceObject = i$.next();
Object destinationObject = dozer.map(sourceObject, destinationClass);
destinationList.add(destinationObject);
}
return destinationList;
}
public static void copy(Object source, Object destinationObject) {
dozer.map(source, destinationObject);
}
}
然后一句話轉(zhuǎn)完
List<T> list = BeanMapper.mapList(data,target);
總結(jié)
第一次用markdown寫文章壮啊,感覺倍爽,代碼應該還有不完善的地方撑蒜,其實最好的方式是自定義一個注解歹啼,然后加上校驗,可是由于知識水平的限制座菠,暫時沒有什么進展狸眼。
完整的代碼
https://github.com/Mygraduate/Supervisor_Java.git