6 1. 엑셀파일로 내보내기
hanmj edited this page 7 months ago

데이터를 엑셀파일에 저장하려면

  • cokr.xit.base.docs.xls.XLSWriter를 생성
  • 워크시트를 생성
  • 데이터를 설정할 셀(행과 열)의 선택
  • 데이터를 설정

한다.

다음은 XLSWriter의 사용법을 XLSTest.java 참고하여 설명한다.

XLSWriter

XLSWriter는 다음과 같이 생성한다.

XLSWriter xlsx = new XLSWriter();

워크시트

엑셀파일에 데이터를 설정하려면 워크시트를 생성해야 한다.

워크시트는 인덱스(0부터 시작)로 생성하거나

xlsx.worksheet(0);

이름으로 만들 수 있다.

xlsx.worksheet("첫번째 워크시트");

이상은 다음처럼 간단히 할 수 있다.

XLSWriter xlsx = new XLSWriter().worksheet(0);

또는

XLSWriter xlsx = new XLSWriter().worksheet("워크시트 이름");

열 폭 자동설정

워크시트의 열들의 폭을 자동설정하려면 워크시트를 생성한 후

  • XLSWriter.trackWidth(int...)를 호출한다.
  • 데이터 설정이 완료된 후 XLSWriter.autoWidth()를 호출한다. 다음은 워크시트의 1, 2, 3, 4열의 데이터 길이를 추적하도록 설정하는 예다.
xlsx.trackWidth(0, 1, 2, 3);

또는

XLSWriter xlsx = new XLSWriter().worksheet(0).trackWidth(0, 1, 2, 3);

셀스타일

셀의 스타일 지정은 org.apache.poi.ss.usermodel.CellStyle로 한다. xit-docs는 CellStyle을 쉽게 쓸 수 있도록 StyleFormat을 제공한다.

다음은 Style과 Format을 초기화하는 예다.

Format format = new Format(xlsx);
CellStyle
    numeric = format.n_nn0(), // 숫자 천단위 구분, 우측 정렬
    datetime = format.yyyy_mm_dd_hh_mm_ss(), // 날짜 시간(yyyy-MM-dd HH:mm:ss)
    blue = format.cellStyle(new Style() // 배경색 Blue, 폰트색 White인 CellStyle
        .foregroundColor(IndexedColors.LIGHT_BLUE.getIndex())
        .configure(style -> {
            Font font = format.font();
            font.setColor(IndexedColors.WHITE.getIndex());
            style.font(font);
        }));
  • Format은 자주 쓰이는 CellStyle을 반환하는 메소드를 정의하고 있다.
  • 그외의 다른 셀스타일은 Style을 생성 후 설정하고 Format.cellStyle(Style) 메소드로 CellStyle을 만들어 사용한다.

파일 쓰기

파일 저장

엑셀파일을 저장하려면 XLSWriter.write(String)을 사용한다.

xlsx.write("C:\\my-dir\\엑셀파일.xlsx");

다운로드

'downloadView'로 다운로드하려면 콘트롤러의 메소드에서 다음과 같이 한다.

public ModelAndView doSomething() {
    xlsx = ...;
    ....
    return new ModelAndView("downloadView")
        .addObject("download", xlsx.getDownloadable().setFilename("엑셀파일.xlsx"));
}

AbstractController를 상속하는 콘트롤러에서 직접 다운로드하려면 다음과 같이 한다.

public void doSomething(..., HttpServletResponse hresp) {
    xlsx = ...;
    ....
    download(xlsx.getDownloadable().setFilename("엑셀파일.xlsx"), hresp);
}

데이터 설정

행, 열, 셀의 선택

데이터를 설정하려면 데이터가 위치할 행과 열을 0부터 시작하는 인덱스로 선택한다.

xlsx.row(1)  //두번째 행 선택
    .col(1); //두번째 열 선택

위 코드는 다음처럼 간단히 할 수 있다.

xlsx.cell(1, 1);

행의 선택은 인덱스가 증가하는 방향으로만 할 수 있다. 즉,

xlsx.cell(127, 1); // 128번째 행 선택

위와 같이 128번째 행을 선택한 후 다시 이전 행을 선택할 수 없다.

xlsx.cell(5, 1); // 행의 인덱스가 감소하는 방향으로는 선택할 수 없다.

이는 XLSWriter가 의존하는 SXSSFWorkbook이 성능 상의 이유로 허용하지 않기 때문이다.

데이터 개별 설정

데이터와 스타일을 하나씩 설정하려면 XLSWriter.value(Object)를 사용한다.

다음은 첫번째 행의 1열부터 3열까지 '테스트 0', 1234567890, 현재 날짜를 설정하는 예다.

Date now = new Date();

xlsx.row(0)
    .col(0).value("테스트 0-0")
    .col(1).value("테스트 0-1")
    .col(2).value(1234567890)
    .col(3).value(now)

숫자, 날짜시간 스타일을 적용하려면 다음과 같이 한다.

xlsx.row(0)
    .col(0).value("테스트 0-0")
    .col(1).value("테스트 0-1")
    .col(2).value(1234567890).value(numeric) // 숫자 스타일
    .col(3).value(now).value(datetime); // 날짜시간 스타일

행(row) 데이터 설정

행 하나에 여러 데이터와 스타일을 설정하려면 XLSWriter.rowValues(Iterable<?>)를 사용한다.

다음은 두번째 행의 1열부터 '테스트 1', 2345678901, 현재 날짜를 설정하는 예다.

xlsx.cell(1, 0)
    .rowValues(List.of("테스트 1-0", "테스트 1-1", 234567890, now));

숫자, 날짜시간 스타일을 적용하려면 다음과 같이 한다.

xlsx.cell(1, 0)
    .rowValues(List.of("테스트 1-0", "테스트 1-1", 234567890, numeric/* 숫자 스타일 */, now, datetime /* 날짜시간 스타일*/));

스타일은 적용하려는 값의 다음에 표시한다.

Map의 데이터를 행 하나에 설정하려면 다음과 같이 한다.

DataObject map = new DataObject()
    .set("field1", "테스트 2-0")
    .set("field2", "테스트 2-1")
    .set("field3", 34567890)
    .set("field4", now);

xlsx.cell(2, 0)
    .rowValues(map);

Map의 데이터를 선택/가공하거나 스타일을 적용하려면 다음과 같이 한다.

UnaryOperator<Object> concatField1_2 = obj -> { // 데이터 가공: field1 + field2
    DataObject rec = (DataObject)obj;
    return rec.string("field1") + " " + rec.string("field2");
};

xlsx.cell(2, 0)
    .rowValues(
        map,
        format
            .value(concatField1_2)
            .style(blue),
        "", // 공백
        "field3", // 키 field3의 값
        numeric,  // 숫자 스타일
        "field4", // 키 field4의 값
        datetime  // 날짜시간 스타일
    )
    .merge(0, 1) // 현재 행의 1, 2열 병합;

  • 맵 다음에 지정하는 객체들의 갯수만큼 데이터를 설정한다.
  • 맵의 키(예: field3, field4)를 지정하면 해당 키의 데이터를 설정한다.
  • ""을 지정하면 해당 위치는 공란으로 설정한다.
  • 데이터를 가공하여 설정하려면 Format.value(UnaryOperator)를 사용한다.

객체 데이터 목록 설정

객체목록의 데이터들을 설정하려면 XLSWriter.values(Iterable, Function)을 사용한다.

다음은 사용예다.

static class TestObject {
    private String
        field1,
        field2;
    private int field3;
    private Date date;

    //Getters & Setters
    ...
}
...
List<TestObject> objs = IntStream.range(0, 10).boxed()
    .map(i -> new TestObject()
        .setField1("테스트 " + (6 + i) + "-0")
        .setField2("테스트 " + (6 + i) + "-1")
        .setField3(i * 1000000)
        .setDate(now)
    )
    .toList();

xlsx.cell(5, 0) // 6번째 행, 1번째 열 선택
    .values(
        objs, // 객체목록
        obj -> List.of( // 객체 하나의 필드값들과 스타일을 반환하는 함수
            obj.getField1(), // 객체의 field1 값
            obj.getField2(), // 객체의 field2 값
            obj.getField3(), // 객체의 field3 값
            numeric,         // field3을 숫자 스타일로 설정
            obj.getDate(),   // 객체의 date값
            datetime         // date값을 날짜시간 스타일로 설정
        )
    );
xlsx.autoWidth(); // 추적한 열들의 폭 자동설정

Map 데이터 목록 설정

맵 목록의 데이터들을 설정하려면 XLSWriter.values(Iterable, Object...)을 사용한다.

다음은 사용예로 '두번째 시트'에 데이터를 설정하기로 한다.

List<DataObject> dataset = IntStream.range(0, 10).boxed()
    .map(i -> (DataObject)new DataObject()
        .set("field1", "테스트 " + (17 + i) + "-0")
        .set("field2", "테스트 " + (17 + i) + "-1")
        .set("field3", i * 1000000)
        .set("date", now)
    )
    .toList();

xlsx.worksheet("두번째 시트").trackWidth(0, 1, 2, 3, 4); //'두번째 시트' 생성, 1 ~ 5열의 데이터 길이를 추적하도록 

xlsx.cell(0, 0)
    .values(dataset); // 맵의 모든 키에 대한 데이터들을 설정한다.

xlsx.cell(11, 0)
    .values(
        dataset,
        format    // 첫번째 열의 값과 CellStyle 설정
            .value(concatField1_2)
            .style(blue) // 셀의 배경과 글자색
            .onCell(obj -> xlsx.merge(0, 1)), // 현재 행의 1, 2열을 병합
        "",       // 공백
        "field3", // 키 field3의 값
        numeric,  // field3의 값을 숫자 스타일로 설정
        "date",   // 키 date의 값
        datetime  // date값을 날짜시간 스타일로 설정
    );

이미지 메모 설정

셀에 이미지 메모를 설정하려면 Comment.setImageComment(String)Format.onCell(Consumer)로 다음과 같이 설정한다.

아래 예의 이미지 파일은 테스트 리소스로 제공되는 것을 사용한다.

String dir = "...",
       path1 = dir + "4148020220006850A.jpg",
       path2 = dir + "4148020220006869A.jpg";
dataset = IntStream.range(0, 10).boxed()
    .map(i -> (DataObject)new DataObject()
        .set("field1", "테스트 " + (27 + i) + "-0")
        .set("field2", "테스트 " + (27 + i) + "-1")
        .set("field3", i * 1000000)
        .set("pic", i % 2 == 0 ? path1 : path2)
        .set("date",now)
    )
    .toList();

CellStyle currency = format.cellStyle(new Style().dataFormat("#,##0 원").merge(Style.RIGHT));
Comment comment = new Comment(xlsx);
Format picFormat = format.of("pic") // 'pic' 필드를 위한 Format 생성
    .value(obj -> {
        String path = (String)((Map)obj).get("pic");
        return path.substring(path.lastIndexOf("\\") + 1);
    })
    .onCell(obj -> comment.setImageComment((String)((Map)obj).get("pic")));

xlsx.cell(22, 0)
    .values(
        dataset,
        format
            .value(concatField1_2)
            .style(blue)
            .onCell(row -> xlsx.merge(0, 1)),
        "",
        "field3",
        currency,
        picFormat,
        "date",
        datetime
    );

한 행에 Format이 여러 개 필요할 경우 Format.of(Object) 메소드나 new Format(xlsx).key(Object)로 생성하여 지정한다.

CellDef

데이터를 설정할 때 각 열의 이름과 필드이름, 데이터 값, 스타일 등을 한 번에 정의할 때 CellDef를 사용한다.

제목행을 설정할 때는 CellDef.header(List) 메소드를 사용한다.

데이터를 설정할 때는 CellDef.values(List) 메소드로 데이터/스타일을 받아 XLSWriter.values(List, Object...)를 호출한다.

List<CellDef> cellDefs = List.of(
    new CellDef().setLabel("제목").setValue( // 첫번째 열의 제목, 데이터, 스타일
        format
            .value(concatField1_2)
            .style(blue)
            .onCell(row -> xlsx.merge(0, 1))
    ),
    CellDef.EMPTY, // 두번째 열은 빈 열
    new CellDef().setLabel("금액").setField("field3").setValue(numeric), // 세번째 열의 제목, 데이터 필드, 스타일
    new CellDef().setLabel("사진").setValue(picFormat), // 네번째 열의 제목과 이미지 설정
    new CellDef().setLabel("등록일시").setField("date").setValue(datetime) // 다섯번째 열의 제목, 데이터 필드, 스타일
);

xlsx.cell(33, 0)
    .rowValues(CellDef.header(cellDefs, null)) // 각 열의 제목 설정
    .merge(0, 1)
    .cell(34, 0)
    .values(dataset, CellDef.values(cellDefs)); // CellDef를 이용해 dataset의 데이터 설정
    
xlsx.autoWidth(); // 추적한 열들의 폭 자동설정