행복한 아빠

Java로 엑셀 다운로그 구현하기 본문

웹기술들

Java로 엑셀 다운로그 구현하기

행복한아빠 2009. 12. 20. 21:30

프로젝트를 하다보면 데이터를 엑셀로 출력해 달라는 요구가 빈번히 발생합니다.
간단하게 CSV 파일로 출력할 수도 있으며 Excel이 HTML 형태의 문서양식도 지원하기에
HTML로 작성하고 mine type만 살짝 바꿔주어 엑셀로 읽을 수 있도록 하는 방법이 있습니다.

물론 CSV로 요구사항을 충분히 만족시킬 수 있을 경우도 있으나 고품질의 엑셀 양식을 요구할 경우 위 두 방법은 웬지 부족함이 있습니다. 이럴 경우 엑셀 포맷으로 출력할 필요가 있는데 여러가지 솔루션이 있어 어렵지 않게 해결할 수 있는 부분이지만 매번 어떻게 처리할 지 고민을 합니다. 여러가지 방법을 사용해 본 후 가장 나은 방법을 추천해 봅니다.


솔루션 찾기

Java로 엑셀을 다루기 위한 방법은 여러가지 있으며 대표적인 것 3가지만 살펴보겠습니다.
Java Excel API은 개발자가 엑셀 스프레드시트를 동적으로 읽고, 쓰고 수정할 수 있도록 하는 성숙한 오픈소스 java API입니다. Java 개발자는 간단한 API를 이용하여 엑셀 스프레드시트를 읽고, 수정하고 쓸 수 있습니다.

장점은 이 패키지는 다른 패키지를 필요로 하지 않고 현재버전(2.6.9.1)의 jar 파일의 크기가 709KB로 부담없이 쓸 수 있다는 것입니다. 비교적 간단한 AP를 제공하나 엑셀의 차트나 그래프 매크로 정보를 생성할 수는 없으며 시트에 PNG 이미지만 추가할 수 있습니다.

POI는 Microsoft의 OLE 2 컴포넌트 문서 포맷을 다루기 위한 프로젝트입니다. 따라서 POI에는 엑셀 뿐만 아니라 워드문서를 다루는 API 도 제공하고 몇가지 모듈로 나누어져 있습니다.
이중 HSSF 가 엑셀 파일 포맷을 다루기 위한 자바구현체입니다. 오래되었고 다른 것 보다 큰 이상을 가지고 출발한 프로젝트라 다양한 API를 제공합니다.

apache 재단에서 진행되는 프로젝트이며 POI 패키지는 여러개의 다른 패키지(commons, log4j 같은..)를 필요로 합니다. 풍부한 API를 제공하는 대신에 사용하기 번거로운 점이 있으며 많은 패키지를 필요로 한다는 부담이 있습니다.

jXLS은 엑셀파일 포맷의 템플릿을 이용하여 엑셀 파일을 손쉽게 생성하기 위한 패키지입니다. 또한 XML 설정 파일을 통해 엑셀파일의 데이터를 Java 객체로 읽는 장치도 제공합니다.

사실 jXLS은 Javarta POI 패키지를 기반으로 동작합니다. 따라서 jXLS을 사용하기 위해서는 POI가 사용하는 많은 다른 패키지를 필요로 합니다.

반면 jXLS 자체는 매우 작으며 복잡한 보고서 생성이나 일정한 양식의 엑셀 데이터를 규칙에 따라 읽게 한다는 뚜렷한 목적이 있어 범용성은 약간 떨어지더라도 대부분의 엑셀 관련 작업에 훌륭한 솔루션이 될 수 있습니다.

우리는 jXLS을 이용하여 위 문제를 풀어볼 것입니다.


jXLS 맛보기

jXLS은 템플릿을 기반으로 최종 엑셀파일을 생성합니다.
JSP나 Velocity 또는 Freemarker 같이 템플릿을 만들고 출력할 데이터를 템플릿을 이용하여 변환하면 템플릿 모양대로 최종결과물이 생성하는 구조입니다.
여기서 jXLS은 템플릿으로 엑셀파일을 그대로 쓰며 따라서 템플릿 작성이 매우 쉽다. 또한 엑셀 파일을 그대로 사용하므로 엑셀의 서식과 차트등 엑셀 파일의 대부분의 기능을 그대로 사용할 수 있습니다.



간단한 예제로 설명을 하겠습니다.

Java 객체
출력할 데이터를 만듭니다. 아래는 Customer 클래스를 예로 사용했지만 Map도 지원합니다. 각 속성에 대한 getter, setter는 반드시 존재해야 합니다.
public class Customer {
    private Long no;
    private String name;
    private String cellphone;
    private String email;
    public Long getNo() {
        return no;
    }
    ...
}


변환
출력한 데이터를 만든 후 엑셀템플릿과 출력할 데이터 (Java 빈)을 이용하여 변환합니다.
        // 출력할 객체를 만든다.
        List<Customer> customers = new ArrayList<Customer>();
        Customer customer = new Customer();
        customer.setNo(1L);
        ...
        customers.add(customer);
        ...
       
        Map<String, Object> beans = new HashMap<String, Object>();
        beans.put("customers", customers);
        XLSTransformer transformer = new XLSTransformer();
        transformer.transformXLS("엑셀템플릿파일이름.xls", beans, "엑셀결과파일이름.xls");


엑셀템플릿
엑셀파일로 다음과 같이 작성합니다. 중간에 ${..}로 들어갈 곳은 데이터가 치환되는 부분입니다. 위의 경우 "엑셀템플릿파일이름.xls" 파일을 아래와 같이 생성합니다.


이제 프로그램을 구동하면 자바객체를 이용하여 엑셀파일을 생성할 것입니다.

태그 사용하기
좀 더 세밀한 제어를 위해 jXLS은 여러가지 태그를 제공합니다. 위의 예제를 태그로 변경하면 다음과 같습니다.


jXLS의 자세한 사용법은 jXLS 홈페이지를 참조하세요.


웹환경 실전에서

우리는 경우에 따라 고객목록을 HTML로 출력하거나 엑셀파일로 다운로드할 것입니다. 즉 동일한 데이터가 경우에 따라 표현하는 방법 만 달리하는 경우에 해당합니다.
이런 요구사항은 흔히 발생하고 이런 경우 MVC 모델을 응용한 아키텍처를 많이 사용합니다.
MVC(Model View Controller) 모델은 많이 들어보았을 것입니다. 여기서 우리는 Model과 View를 분리하는 작업을 할 것입니다. 위의 경우 Model은 Customer 클래스에 해당하고 View는 엑셀파일에 해당할 것입니다.



Struts2 Result 구현
위 구현을 위해 우리는 Struts2를 사용할 것입니다. Struts2는 가장 많이 사용하는 프레임워크로 Struts 1에 비해 구조가 많이 개선되었습니다. 위의 목적을 달성하기 위해 우리는 데이터를 엑셀로 만드는 Result Type만 구현하여 Struts2 프레임워크에 붙여(플러그인)주기만 하면 됩니다.


Excel Result Type 만들기
Struts2에 새로운 Result Type을 만들기 위해서는 com.opensymphony.xwork2.Result를 구현하면 됩니다.

엑셀로 변환하기 위해 필요한 정보는 엑셀 템플릿, 엑셀에 출력할 객체들 그리고 다운로드받을 파일이름 정도일 것입니다. 이것들을 이용하여 변환을 합니다.

JXLSResult.java

아래는 제가 사용하는 Struts2 엑셀 result type 클래스입니다.

public class JXLSResult implements Result {
    /** 엑셀 템플릿 */
    private String template;
    /** 엑셀에 출력할 객체들 */
    private String beans;
    /** 파일이름을 얻어올 키값 */
    private String filenameKey = "filename";

    public void execute(ActionInvocation invocation) throws Exception {
        ActionContext actionContext = invocation.getInvocationContext();
        ServletContext context
            = (ServletContext) actionContext.get(StrutsStatics.SERVLET_CONTEXT);
        HttpServletResponse response
            = (HttpServletResponse) actionContext.get(StrutsStatics.HTTP_RESPONSE);
        // 출력할 bean들을 만든다.
        Map<String, Object> beanParams = new HashMap<String, Object>();
        String[] beanNames = splitBeans();
        for (String beanName : beanNames) {
            beanParams.put(beanName, invocation.getStack().findValue(beanName));
        }
        XLSTransformer transformer = new XLSTransformer();
        InputStream is = null;
        HSSFWorkbook workbook;
        String finalTemplate = TextParseUtil.translateVariables(this.template, invocation.getStack());
        try {
            is = readTemplate(finalTemplate, context);
            workbook = transformer.transformXLS(is, beanParams);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // 무시
                }
            }
        }
        String filename = invocation.getStack().findString(filenameKey);
        if (filename == null)
            filename = "기본파일이름";
        writeWorkbook(filename, response, workbook);
    }
    /** 엑셀에 출력할 객체이름(key)들을 분리한다. */
    private String[] splitBeans() {
        return this.beans.split(",");
    }
    /** 엑셀 결과를 출력한다. */
    private void writeWorkbook(
        String filename, HttpServletResponse response, HSSFWorkbook workbook)
        throws IOException {
        response.setHeader(
            "Content-disposition", "attachment;filename=" + encodeFileName(filename + ".xls"));
        response.setContentType("application/x-msexcel");
        workbook.write(response.getOutputStream());
    }
    /** 파일이름 인코딩 */
    private String encodeFileName(String filename) {
        try {
            return URLEncoder.encode(filename, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e.getMessage, e);
        }
    }
    /** 엑셀 템플릿을 읽는다. */
    private InputStream readTemplate(
        String finalTemplate, ServletContext context) throws FileNotFoundException {
        String templateFilePath = context.getRealPath(finalTemplate);
        return new FileInputStream(templateFilePath);
    }
    /**
     * @param template 엑셀 템플릿
     */
    public void setTemplate(String template) {
        this.template = template;
    }
    /**
     * @param beans 엑셀에 출력할 객체들
     */
    public void setBeans(String beans) {
        this.beans = beans;
    }
    /**
     * @param filenameKey 파일이름을 얻어올 키값
     */
    public void setFilenameKey(String filenameKey) {
        this.filenameKey = filenameKey;
    }
}



Struts2에 Result Type 추가
struts.xml 에 구현한 result type을 추가합니다. 상세한 result type 구현 및 추가방법은 struts 사이트를 참조하세요.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
 ...
 <package name="my-default" extends="struts-default">
  <result-types>
   <result-type name="excel" class="com.nextree.fw.commonweb.struts.result.JXLSResult" />
  </result-types>
 ...
 </package>
</struts>



Controller 작성
Struts2의 Action을 controller로 사용하겠습니다. 아래와 같이 Action을 만듭니다. 참고로 struts2는 POJO를 그대로 Action으로 사용할 수 있습니다. 자세한 Struts2 Action 작성방법은 Struts2 site를 참조하십시오.
public class CustomerListController {
    /** 반환할 값 */
    private List<Customer> customers;

    public String execute() {
        ....
        this.customers = ....;   // 여기서 출력해야 할 데이터를 조회한다.
        return Action.SUCCESS;
    }
    /* 출력할 값의 getter 메소드를 반드시 제공한다. */
    public List<Customer> getCustomers() {
        return this.customers;
    }
    /* 위 Excel Result type의 경우 file이름을 controller에서 가져오게 되어 있다. */
    public String getFilename() {
        return "고객목록";
    }
}


Action 정의
Struts2 의 struts.xml에 action을 정의합니다. 우리는 excel로 다운로드하기에 앞에 설치한 excel result type을 사용할 것입니다.
 
...
<package name="mypackage" namespace="/mypackage" extends="my-default">
  <action name="customers" class="test.CustomerListController" method="execute">
   <result type="excel">
    <param name="template">/customer/CustomerList.xls</param>
    <param name="beans">customers</param>
    <param name="filenameKey">filename</param>
   </result>
  </action>
...

이제 http://hostname:port/context/mypackage/customers.do 로 접속하면 Excel 파일을 다운로드 할 것입니다.


결론

위의 방법을 사용하면 그냥 JSP 작성하듯이 엑셀양식을 작성하여 엑셀을 다운로드하는 기능을 구현하기가 매우 쉽습니다.
엑셀을 그대로 사용하기 때문에 엑셀에 화려한 서식뿐만 아니라 차트나 수식등을 넣을 수도 있어 고품질의 엑셀파일을 만들 수 있습니다.


1 Comments
댓글쓰기 폼