JVM 환경에서 Excel 보고서를 만들어 본 개발자라면 Apache POI의 고통을 잘 알고 있을 것입니다.
// Apache POI로 직접 작성하면...
val workbook = XSSFWorkbook()
val sheet = workbook.createSheet("직원 현황")
val headerRow = sheet.createRow(0)
headerRow.createCell(0).setCellValue("이름")
headerRow.createCell(1).setCellValue("직급")
headerRow.createCell(2).setCellValue("연봉")
employees.forEachIndexed { index, emp ->
val row = sheet.createRow(index + 1)
row.createCell(0).setCellValue(emp.name)
row.createCell(1).setCellValue(emp.position)
row.createCell(2).setCellValue(emp.salary.toDouble())
}
// 열 폭 조정, 스타일 적용, 수식 추가, 차트... 끝이 없음
헤더 하나 만들고, 셀 하나 채우고, 스타일 적용하고... 보고서 하나에 수백 줄의 코드가 필요합니다.
거기에 차트, 조건부 서식, 수식, 이미지까지 추가하면? 코드는 걷잡을 수 없이 불어납니다.
TBEG (Template Based Excel Generator) 은 이 문제를 근본적으로 해결합니다.
Excel 템플릿에 데이터를 바인딩하여 보고서를 생성하는 JVM 라이브러리입니다.
핵심 아이디어는 간단합니다:
1. 디자이너(또는 본인)가 Excel에서 직접 보고서 양식을 디자인합니다
2. 데이터가 들어갈 자리에 ${변수명} 마커를 넣습니다
3. 코드에서는 데이터만 전달합니다
4. TBEG이 템플릿 + 데이터를 결합하여 최종 보고서를 생성합니다
// TBEG 사용 - 이게 전부입니다
val data = mapOf(
"title" to "직원 현황",
"employees" to employeeList
)
ExcelGenerator().use { generator ->
val bytes = generator.generate(template, data)
File("output.xlsx").writeBytes(bytes)
}
서식, 차트, 수식, 조건부 서식은 모두 템플릿에서 관리합니다. 코드는 데이터 바인딩에만 집중합니다.
Excel이 이미 잘하는 기능은 재구현하지 않고 그대로 살립니다.
=SUM()으로, 평균은 =AVERAGE()로익숙한 Excel 기능을 그대로 활용하세요.
TBEG은 여기에 동적 데이터 바인딩을 더하고, 데이터가 확장되어도 이 기능들이 의도대로 동작하도록 자동 조정합니다.

val data = simpleDataProvider {
value("reportTitle", "Q1 2026 매출 실적 보고서")
value("period", "2026년 1월 ~ 3월")
value("author", "황용호")
value("reportDate", LocalDate.now().toString())
image("logo", logoBytes)
imageUrl("ci", "https://example.com/ci.png") // URL도 가능
items("depts") { deptList.iterator() }
items("products") { productList.iterator() }
items("employees") { employeeList.iterator() }
}
ExcelGenerator().use { generator ->
generator.generateToFile(template, data, outputDir, "quarterly_report")
}

변수 치환, 이미지 삽입, 반복 데이터 확장, 자동 셀 병합, 수식 범위 조정, 조건부 서식 자동 적용, 차트 데이터 반영까지 TBEG이 자동으로 처리합니다.
| 기능 | 설명 |
|---|---|
| 템플릿 기반 생성 | Excel 템플릿에 데이터를 바인딩하여 보고서 생성 |
| 반복 데이터 처리 | ${repeat(...)} 문법으로 리스트 데이터를 행/열로 확장 |
| 변수 치환 | 셀, 차트, 도형, 머리글/바닥글, 수식 인자 등에 값 바인딩 |
| 이미지 삽입 | 바이트 배열 또는 URL로 동적 이미지 삽입 |
| 자동 셀 병합 | 반복 데이터에서 연속된 같은 값의 셀을 자동 병합 |
| 요소 묶음 | 여러 요소를 하나의 단위로 묶어 일체로 이동 |
| 선택적 필드 노출 | 상황에 따라 특정 필드의 노출을 제한. 삭제(DELETE) 또는 비활성화(DIM) 모드 선택 가능 |
| 수식 자동 조정 | 데이터 확장 시 =SUM(범위), =AVERAGE(범위) 등 수식 범위를 자동 갱신 |
| 조건부 서식 자동 적용 | 반복되는 셀에 원본의 조건부 서식을 자동 적용 |
| 차트/피벗 테이블 자동 반영 | 확장된 데이터 범위를 차트에 자동 반영. 피벗 테이블 소스 범위 자동 조정 |
| 파일 암호화 | 생성된 Excel 파일에 열기 암호 설정 |
| 문서 메타데이터 | 제목, 작성자, 키워드 등 문서 속성 설정 |
| 대용량 처리 | 100만 행 이상의 데이터를 낮은 CPU 사용률로 안정적으로 처리 |
| 비동기 처리 | 대용량 데이터를 백그라운드에서 처리 |
| 지연 로딩 | DataProvider를 통한 메모리 효율적 데이터 처리 |
| Spring Boot 지원 | Auto-configuration으로 간편한 연동 |
| 문법 | 설명 | 예시 |
|---|---|---|
| ${변수명} | 변수 치환 | ${title} |
| ${객체.필드} | 반복 항목 필드 | ${emp.name} |
| ${repeat(컬렉션, 범위, 객체)} | 반복 처리 | ${repeat(items, A2:C2, item)} |
| ${image(이름)} | 이미지 삽입 | ${image(logo)} |
| ${merge(객체.필드)} | 자동 셀 병합 | ${merge(emp.dept)} |
| ${bundle(범위)} | 요소 묶음 | ${bundle(A5:H12)} |
| ${hideable(객체.필드, 범위, 모드)} | 선택적 필드 노출 | ${hideable(emp.salary, C1:C3, DIM)} |
TBEG은 내부적으로 스트리밍 방식으로 데이터를 생성하기 때문에, 100만 행을 약 9초에 생성하면서도 시스템 CPU의 9% 미만만 사용합니다. 렌더링과 후처리 모두 스트리밍 방식으로 동작하여 데이터 크기에 관계없이 일정한 메모리 버퍼만 사용합니다.
| 데이터 크기 | 소요 시간 | CPU 사용률 |
|---|---|---|
| 1,000행 | 20ms | 23.5% |
| 10,000행 | 109ms | 14.7% |
| 30,000행 | 315ms | 12.5% |
| 100,000행 | 993ms | 10.8% |
| 1,000,000행 | 8,952ms | 8.8% |
| 라이브러리 | 소요 시간 |
|---|---|
| TBEG | 0.3초 |
| JXLS | 5.2초 |
실무에서 TBEG이 가장 많이 쓰이는 곳은 웹 애플리케이션의 엑셀 다운로드 기능입니다.
@GetMapping("/api/reports/download")
fun downloadReport(response: HttpServletResponse) {
val data = reportService.getReportData()
val template = resourceLoader.getResource("classpath:templates/report.xlsx")
response.contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
response.setHeader("Content-Disposition", "attachment; filename=report.xlsx")
excelGenerator.generate(template.inputStream, data)
.let { response.outputStream.write(it) }
}
관리자 페이지의 목록 내보내기, 정산 보고서 다운로드, 월간 리포트 생성 등...
템플릿 하나 만들어 두면 코드는 이게 전부입니다.
✅ 정형화된 보고서/명세서 생성
✅ 디자이너가 제공한 Excel 양식에 데이터 채우기
✅ 복잡한 서식(조건부 서식, 차트)이 필요한 보고서
✅ 수만~수십만 행의 대용량 데이터 처리
✅ 행/열 구조가 동적으로 변하는 표 만들기
✅ Spring Boot 환경에서의 Excel 다운로드 API 구현
❌ Excel 파일 읽기/파싱 (TBEG은 생성 전용)
// build.gradle.kts
dependencies {
implementation("io.github.jogakdal:tbeg:1.2.3")
}
// Gradle (Groovy DSL)
dependencies {
implementation 'io.github.jogakdal:tbeg:1.2.3'
}
<!-- Maven -->
<dependency>
<groupId>io.github.jogakdal</groupId>
<artifactId>tbeg</artifactId>
<version>1.2.3</version>
</dependency>
위 버전은 이 글의 작성 시점 기준입니다. 최신 버전은 Maven Central에서 확인하세요.
import io.github.jogakdal.tbeg.ExcelGenerator
import java.io.File
data class Employee(val name: String, val position: String, val salary: Int)
fun main() {
val data = mapOf(
"title" to "직원 현황",
"employees" to listOf(
Employee("황용호", "부장", 8000),
Employee("한용호", "과장", 6500)
)
)
ExcelGenerator().use { generator ->
val template = File("template.xlsx").inputStream()
val bytes = generator.generate(template, data)
File("output.xlsx").writeBytes(bytes)
}
}
import io.github.jogakdal.tbeg.ExcelGenerator;
import java.io.*;
import java.nio.file.*;
import java.util.*;
public class Example {
public static void main(String[] args) throws Exception {
var data = Map.<String, Object>of(
"title", "직원 현황",
"employees", List.of(
Map.of("name", "황용호", "position", "부장", "salary", 8000),
Map.of("name", "한용호", "position", "과장", "salary", 6500)
)
);
try (var generator = new ExcelGenerator()) {
byte[] bytes = generator.generate(
new FileInputStream("template.xlsx"), data);
Files.write(Path.of("output.xlsx"), bytes);
}
}
}
이 글은 TBEG 시리즈의 첫 번째 편입니다. 다음 편에서는 실제 템플릿을 만들고 데이터를 바인딩하는 과정을 단계별로 다룹니다.
GitHub: jogakdal/data-processors-with-excel
Maven Central: tbeg
📖 상세 문서