Spring 엑셀 다운로드 -2 모듈화와 Reflection

2022. 6. 14. 18:39Spring/Spring 실습

https://brilliantdevelop.tistory.com/125?category=1018911에서 

FreeBoard테이블 엑셀 다운로드를 하였다.

이제 Member테이블에서도 엑셀 다운로드를 하려고 한다.

Controller에 다음과 같이 member에서의 excel다운로드기능의 메소드를 추가하려고 한다.

 

 

ExcelControoler의 excelDownloadMember 메소드

	@RequestMapping("/member/excelDown")
		public void excelDownloadMember(HttpServletResponse response, @ModelAttribute("searchVO")SearchVO searchVO
         , @ModelAttribute("searchJob") String searchJob
            , @ModelAttribute("searchHobby") String searchHobby) throws IOException{
			XSSFWorkbook wb=null;
			Sheet sheet=null;
			Row row=null;
			Cell cell=null; 
			wb = new XSSFWorkbook();
			sheet = wb.createSheet("freeBoard");
			List<MemberVO> memberList=excelService.getMemberList(searchVO,searchJob,searchHobby);
			
			//첫행   열 이름 표기 
			int cellCount=0;
			row = sheet.createRow(0); //0번째 행
			cell=row.createCell(cellCount++);
			cell.setCellValue("member아이디");
			cell=row.createCell(cellCount++);
			cell.setCellValue("member이름");
			cell=row.createCell(cellCount++);
			cell.setCellValue("member비밀번호");
			// .........생략    테이블 추가할 때 마다 이 과정 계속 써줘야하나?
		
		
			for(int i=0; i < memberList.size() ; i++  ) {
				row=sheet.createRow(i+1);  // '열 이름 표기'로 0번째 행 만들었으니까 1번째행부터
				cellCount=0; //열 번호 초기화
				cell=row.createCell(cellCount++);
				cell.setCellValue(memberList.get(i).getMemId());
				cell=row.createCell(cellCount++);
				cell.setCellValue(memberList.get(i).getMemName());
				cell=row.createCell(cellCount++);
				cell.setCellValue(memberList.get(i).getMemPass());
				//........ 테이블 추가할 때 마다 이 과정 계속 써줘야 하나?
			}
			
			
	
			// 컨텐츠 타입과 파일명 지정
			response.setContentType("ms-vnd/excel");
			response.setHeader("Content-Disposition", "attachment;filename=member.xlsx");  //파일이름지정.
			//response OutputStream에 엑셀 작성
			wb.write(response.getOutputStream());
		}

테이블별로 VO가 다른데 cell에 데이터 집어넣을때마다 매번 get메소드를 직접 써주기 너무 힘들다.만약 VO의 필드가 100개라면 100개 다 써야하나??이를 Java Reflection을 이용해 처리해보도록 하자.Reflection에 대한 기초 사항은 https://brilliantdevelop.tistory.com/85?category=988809를 참고하도록 하자.get메소드를 직접 쓰지 않고도  어떤테이블, 어떤VO가 오더라도 excel다운로드를 지원해주는 util클래스를 만들자.

 

 

먼저 필드에 대한 설명을 지정할 수 있는 @annotation을 만들자.

ExcelColumn.java

package com.study.excel
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelColumn {
  String headerName() default "";
}

 

 

 

이제 이 @ExcelColumn을  FreeBoardVO에 붙이자.

FreeBoardVO


public class FreeBoardVO {


    @ExcelColumn(headerName = "글번호")
    private int    boNo       ;                                           /* 글 번호 */

    @ExcelColumn(headerName = "글 제목")
    private String boTitle     ;                                     /* 글 제목 */
    
    @ExcelColumn(headerName = "글 분류 코드")
    private String boCategory     ;                                  /* 글 분류 코드 */
    
    @ExcelColumn(headerName = "작성자명")
    private String boWriter    ;                                    /* 작성자명 */

    @ExcelColumn(headerName = "비밀번호")
    private String boPass          ;                                      /* 비밀번호 */

    @ExcelColumn(headerName = "글 내용")
    private String boContent    ;                                   /* 글 내용 */

    @ExcelColumn(headerName = "조회수")
    private int    boHit       ;                                          /* 조회수 */

    @ExcelColumn(headerName = "등록 일자")
    private String boRegDate       ;                                   /* 등록 일자 */

    @ExcelColumn(headerName = "수정일자")
    private String boModDate       ;                                   /* 수정 일자 */

    @ExcelColumn(headerName = "삭제여부")
    private String boDelYn      ;                                     /* 삭제 여부 */

 

 

 

 

 

이제  VO가 어떤 필드가 있든 간에 excel을 다운받을 수 있도록 해주는 util프로그램을 만들자.

SimpleExcelFile.java

package com.study.excel;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import com.study.free.vo.FreeBoardVO;
import com.sun.net.httpserver.HttpServer;

public class SimpleExcelFile {
	// Workbook wb = new HSSFWorkbook(); //xls
	private XSSFWorkbook wb; // xlsx
	private Sheet sheet;
	private Row row;
	private Cell cell;
	private Class<?> clazz;
	private List<?> list;

	public SimpleExcelFile(String sheetName, Class<?> clazz, List<?> list)
			throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		wb = new XSSFWorkbook(); // xlsx
		sheet = wb.createSheet(sheetName);
		this.list = list;
		this.clazz = clazz;
		setExcelCoulmnName();
		setExcelBody();
	}

	private void setExcelCoulmnName() {
		row = sheet.createRow(0);
		Field[] fields = clazz.getDeclaredFields(); // 일단 Declared로 했는데 나중에 상속된것관계까지고려한다면 그냥 필드로 바꾸고
													// setAccessible(true)로 바꿔주면 되긴함
		for (int i = 0; i < fields.length; i++) {
			// System.out.println("필드이름 : "+field.getName()+ " 필드 타입:"+field.getType());
			ExcelColumn excelColumn = fields[i].getAnnotation(ExcelColumn.class);
			if (excelColumn != null) {
				cell = row.createCell(i);
				cell.setCellValue(excelColumn.headerName());
			}
		}
	}

	// reflection으로 모든 메소드 실행해야지, get
	private void setExcelBody()
			throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException {
		for (int i = 0; i < list.size(); i++) {
			row = sheet.createRow(i + 1);
			int rowNum = 0;

			
			Field[] fields = clazz.getDeclaredFields();
			for (int j = 0; j < fields.length; j++) {
				cell = row.createCell(rowNum++);
				ExcelColumn excelColumn = fields[j].getAnnotation(ExcelColumn.class);
				if (excelColumn != null) {
						fields[j].setAccessible(true);
						Object obj = fields[j].get(list.get(i));
						if (obj != null)
							cell.setCellValue(obj.toString());
				}
			}

		}
	}

	public void write(OutputStream outputStream) throws IOException {
		wb.write(outputStream);
		// wb.close(); //xlsx는 close자동으로 해줌. xls 할거면 close따로 해주기
	}

}

생성자에서 setExcelCoulmnName(), setExcelBody() 두 메소드를 실행하는데 

이 때 java Reflection을 이용해  excel을 만든다. 

 

 

 

이 SimpleExcelFile을 이용한다면 Controller는 다음과 같이 간단해진다.

(sheet이름도 검색어 적용되도록 살짝 변경했다..)

ExcelController.java

package com.study.excel.web;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

import com.study.common.util.SimpleExcelFile;
import com.study.excel.service.IExcelService;
import com.study.free.vo.FreeBoardSearchVO;
import com.study.free.vo.FreeBoardVO;
import com.study.member.vo.MemberSearchVO;
import com.study.member.vo.MemberVO;

@Controller
public class ExcelController {
	@Inject
	IExcelService excelService;

		@RequestMapping("/free/excelDown")
		public void excelDownload(HttpServletResponse response, @ModelAttribute("searchVO")SearchVO searchVO
        ,@ModelAttribute("searchCategory")String searchCategory)
        throws IOException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
			  List<FreeBoardVO> freeBoardList=excelService.getBoardList(searchVO,searchCategory);

        String sheetName="";
        if( StringUtils.isNotBlank(searchVO.getSearchWord())        ) {
            sheetName=searchVO.getSearchType()+"_"+searchVO.getSearchWord();
        }
        if( StringUtils.isNotBlank(searchCategory) ) {
            if(StringUtils.isNotBlank(searchVO.getSearchWord())) {
                sheetName=sheetName+"_"+searchCategory;
            }else {
                sheetName=sheetName+searchCategory;
            }
        }
        if(StringUtils.isBlank(searchCategory) && StringUtils.isBlank(searchVO.getSearchWord())) {
            sheetName="noSearch";
        }


        SimpleExcelFile simpleExcelFile= new SimpleExcelFile(sheetName, FreeBoardVO.class, freeBoardList);
        //생성자에서 sheet이름, columnName, Body생성


        // 컨텐츠 타입과 파일명 지정
        response.setContentType("ms-vnd/excel");
        response.setHeader("Content-Disposition", "attachment;filename=freeBoard.xlsx");
        simpleExcelFile.write(response.getOutputStream());
		}
		
		

}

 

 

만약 member테이블을 excel다운로드하고싶다면

Controller에  member테이블 excel다운로드요청처리메소드만 만들면 된다.

(+ member테이블을 조회하느 service,dao 메소드도 추가하자)

@RequestMapping("/member/excelDown")
public void excelMemberDown(HttpServletResponse response, @ModelAttribute("searchVO")MemberSearchVO searchVO) throws IOException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
	//엑셀에 실제로 담을 데이터  페이징 없이 전체 데이터를  list로 
	List<MemberVO> memberList=excelService.getMemberList(searchVO); 
	SimpleExcelFile simpleExcelFile= new SimpleExcelFile("memberList", MemberVO.class, memberList);
	//생성자에서 sheet이름, columnName, Body생성
	
		
	// 컨텐츠 타입과 파일명 지정
	response.setContentType("ms-vnd/excel");
	//response.setHeader("Content-Disposition", "attachment;filename=example.xls");
	response.setHeader("Content-Disposition", "attachment;filename=example.xlsx");
	simpleExcelFile.write(response.getOutputStream());
}

 

이로써 어떤 테이블을 excel로 만들더라도

그때 그때 직접 cell이름과 값을 저장하는 코드를 쓰는 것이 아니라 

SimpleExcelFile을 이용해서 한번에 처리하도록 했다.

단 나는 VO에서  field1의 getter가 getField1()이라는 가정하에 작성하였다.