'Struts2'에 해당되는 글 1건

  1. 2009.04.22 | Struts2 에서 엑셀로 저장하기(Result를 엑셀로 지정)
프로젝트에서 자주 나오는 요구사항 중 하나가 엑셀저장이다.
이전 블로그내 포스트에서 언급한적이 있지만 내 경우엔 주로 POI를 통해 엑셀파일을 만들고 서블릿을 통해 다운로드 받을 수 있도록 하거나 스프링에서 제공하는 AbstractExcelView(POI 사용시) 또는 AbstractJExcelView(JExcel 사용시) 라는 View클래스를 상속받아 구현하면 되었다.
스트럿츠2에서도 다양한 ResultType 을 지원하고 있지만 excel이 기본 ResultType으로는 없어서 기냥 첫번째 경우로 다운받도록 했는데 마침 행복한 아빠 님의 블로그에서 jXLS를 이용하여 엑셀을 스트럿츠2의 result type으로 지정하는 방법을 설명하고 있는 것을 보고 잠시 소개해 볼까 한다.
 
프로젝트를 하다보면 데이터를 엑셀로 출력해 달라는 요구가 빈번히 발생한다.
간단하게 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 tyup 클래스이다.
(아래 소스의 저작권은 넥스트리소프트에 있습니다. 참조하여 사용하는 것은 문제가 없으나 저작권은 반드시 명시하여 주시기 바랍니다.)

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(FileServerUtil.getContentTypeOfFile("xls"));
        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 typ을 추가한다. 상세한 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 new JSONResult(this.zipCodeList);
    }
    /* 위 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 작성하듯이 엑셀양식을 작성하여 엑셀을 다운로드하는 기능을 구현하기가 매우 쉽다.
엑셀을 그대로 사용하기 때문에 엑셀에 화려한 서식뿐만 아니라 차트나 수식등을 넣을 수도 있어 고품질의 엑셀파일을 만들 수 있다
 
 
 
 
블로그 이미지

시반

시반(詩伴)이란 함께 시를 짓는 벗이란 뜻을 가지고 있습니다. 함께 나눌수 있는 그런 공간이길 바라며...

카테고리

분류 전체보기 (233)
개발 이야기 (73)
WEB2.0 (57)
DB2 (24)
MySQL (6)
오라클 (26)
기타 (44)
취미 (0)
잡담 (2)