주석이 달린 문서에서 특정 PDF 페이지를 Java로 저장하는 방법

대용량의 주석이 달린 파일에서 특정 PDF 페이지 저장이 필요할 때, Java의 try with resources 패턴과 GroupDocs.Annotation을 함께 사용하면 안전하고 메모리 효율적인 솔루션을 제공한다. 이 튜토리얼에서는 라이브러리 설정, 페이지 범위 추출, 그리고 로직을 Spring Boot 서비스에 통합하는 방법을 보여준다—코드를 깔끔하게 유지하고 리소스를 올바르게 해제하면서.

소개

Annotator는 GroupDocs.Annotation에서 문서를 로드하고 주석 처리 및 저장 메서드를 제공하는 주요 클래스이다.
많은 비즈니스 시나리오—법률 계약서, 기술 매뉴얼, 연구 논문 등—에서 관련 주석이 포함된 몇 페이지만 필요할 때가 있다. 해당 페이지만 추출하면 저장 비용을 최대 96 %까지 절감하고, 다운스트림 처리 속도를 높이며, 허가된 섹션만 공유함으로써 규정 준수를 돕는다.

이 가이드를 마치면 습득하게 될 내용:

  • GroupDocs.Annotation for Java 설치 및 라이선스 적용
  • try with resources를 사용해 페이지 범위를 안전하게 저장
  • 낮은 메모리 오버헤드로 대용량 PDF 처리
  • Spring Boot 문서 서비스에 로직 삽입
  • 파일 잠금 및 메모리 부족 오류와 같은 일반적인 함정 해결

빠른 답변

  • “try with resources java”는 무엇을 하나요? Annotator를 자동으로 닫아 파일 잠금과 메모리 누수를 방지한다.
  • 어떤 라이브러리가 페이지‑범위 저장을 담당하나요? GroupDocs.Annotation은 SaveOptions와 setFirstPage/setLastPage를 제공한다. SaveOptions를 사용해 페이지 범위 및 주석만 포함 여부와 같은 출력 설정을 지정할 수 있다.
  • Spring Boot 서비스에서 사용할 수 있나요? 예 – “Spring Boot 문서 서비스 통합” 섹션을 참고.
  • 라이선스가 필요합니까? 개발 단계에서는 무료 체험판으로 충분하고, 프로덕션에서는 정식 라이선스가 필요하다.
  • 1000페이지 이상의 대용량 PDF에도 안전한가요? load‑only‑annotated‑pages와 배치 처리를 사용하면 메모리 사용량을 낮출 수 있다.

특정 PDF 페이지 저장이란?

특정 PDF 페이지 저장 작업은 원본 문서에서 정의된 페이지 구간을 추출하면서 해당 페이지의 모든 주석을 보존한다. 선택된 페이지만 포함하는 새롭고 작은 PDF가 생성되며, 이는 타깃 공유나 보관에 이상적이다.

페이지 저장에 try resources를 사용하는 이유

try with resources를 사용하면 블록이 종료되는 즉시 Annotator 인스턴스가 해제된다. 이 결정적인 정리는 흔히 발생하는 “파일이 잠김” 예외를 방지하고, 특히 여러 대용량 PDF를 병렬 처리할 때 JVM 힙 사용량을 예측 가능하게 만든다.

사전 요구 사항 및 설정

필요 사항

  • JDK 8+ (권장: JDK 11+)
  • Maven 또는 Gradle을 이용한 의존성 관리
  • GroupDocs.Annotation for Java — 버전 25.2 이상 (50개 이상의 포맷 지원)
  • Java I/O와 OOP에 대한 기본 지식

Maven 구성

pom.xml에 다음 의존성을 추가한다(복사‑붙여넣기가 편리하다):

<!-- ```xml
<repositories>
   <repository>
      <id>repository.groupdocs.com</id>
      <name>GroupDocs Repository</name>
      <url>https://releases.groupdocs.com/annotation/java/</url>
   </repository>
</repositories>
<dependencies>
   <dependency>
      <groupId>com.groupdocs</groupId>
      <artifactId>groupdocs-annotation</artifactId>
      <version>25.2</version>
   </dependency>
</dependencies>
``` -->

Gradle 설정 (Gradle를 선호하는 경우)

// ```gradle
repositories {
    maven {
        url "https://releases.groupdocs.com/annotation/java/"
    }
}

dependencies {
    implementation 'com.groupdocs:groupdocs-annotation:25.2'
}

### 라이선스 설정
무료 체험판으로 시작한 뒤 필요에 따라 임시 또는 정식 라이선스로 전환한다:

- **무료 체험판:** 테스트 및 개발에 적합 – [GroupDocs releases](https://releases.groupdocs.com/annotation/java/)에서 다운로드  
- **임시 라이선스:** 평가 기간을 연장하고 싶다면 [임시 라이선스](https://purchase.groupdocs.com/temporary-license/)를 받으세요  
- **정식 라이선스:** 프로덕션 준비가 되었나요? [여기서 구매](https://purchase.groupdocs.com/buy)  

> **Pro tip:** 체험판은 몇 가지 고급 기능만 제거하므로 이 튜토리얼을 따라하고 개념 증명을 만드는 데 충분하다.

## Java에서 try with resources는 어떻게 작동하나요?

`try` `with` `resources`는 `AutoCloseable`을 구현한 객체에 대해 블록 종료 시 자동으로 `close()`를 호출한다. `Annotator` 인스턴스를 이 구조로 감싸면 라이브러리가 파일 핸들을 해제하고 내부 버퍼를 정리해 추가 코딩 없이 잠금 위험을 없앤다.

## 핵심 구현: 특정 페이지 범위 저장

### `Annotator` 정의 앵커
`Annotator`는 GroupDocs.Annotation의 핵심 클래스이며, 문서 로드·편집·저장을 담당한다. 주석에 접근하고 페이지를 수정하며 결과를 내보내는 메서드를 제공한다.

### 단계 1: 파일 경로 유틸리티 설정

출력 경로를 일관되게 생성하는 작은 헬퍼를 만든다:

```java
// ```java
import org.apache.commons.io.FilenameUtils;

public class FilePathConfiguration {
    public String getOutputFilePath(String inputFile) {
        return "YOUR_OUTPUT_DIRECTORY/SavingSpecificPageRange" + "." + FilenameUtils.getExtension(inputFile);
    }
}

경로 로직을 중앙화하면 디렉터리 변경이 쉬워지고 코드 테스트가 용이해진다.

### 단계 2: 페이지 범위 저장 구현

다음 스니펫은 핵심 로직을 보여준다. `try with resources`를 사용해 정리 작업을 보장한다:

```java
// ```java
import com.groupdocs.annotation.Annotator;
import com.groupdocs.annotation.options.export.SaveOptions;

public class SaveSpecificPageRange {
    public void run(String inputFile) {
        String outputPath = new FilePathConfiguration().getOutputFilePath(inputFile);
        
        try (final Annotator annotator = new Annotator(inputFile)) {
            SaveOptions saveOptions = new SaveOptions();
            saveOptions.setFirstPage(2);  // 페이지 2부터 시작
            saveOptions.setLastPage(4);   // 페이지 4에서 종료
            
            annotator.save(outputPath, saveOptions);
        }
    }
}

- `setFirstPage(2)`와 `setLastPage(4)`는 **포함** 구간을 정의한다(페이지 2‑4).  
- 블록이 종료될 때 `Annotator`가 자동으로 닫혀 파일 잠금 문제가 방지된다.  

### 고급 파일 경로 구성

프로덕션에서는 동적 파일명을 원할 수 있다:

```java
// ```java
public class FilePathConfiguration {
    private final String baseOutputDirectory;
    
    public FilePathConfiguration(String baseOutputDirectory) {
        this.baseOutputDirectory = baseOutputDirectory;
    }
    
    public String getInputFilePath(String filename) {
        return "YOUR_DOCUMENT_DIRECTORY/" + filename;
    }
    
    public String getOutputFilePath(String inputFile, String suffix) {
        String baseName = FilenameUtils.getBaseName(inputFile);
        String extension = FilenameUtils.getExtension(inputFile);
        return String.format("%s/%s_%s.%s", baseOutputDirectory, baseName, suffix, extension);
    }
}

이제 출력 파일은 `contract_pages_2-4.pdf`와 같이 명확한 이름이 붙으며, 어떤 페이지가 추출됐는지 바로 알 수 있다.

## 일반적인 함정 및 회피 방법

### 함정 #1: 페이지 인덱스 혼동
**문제:** 페이지 번호가 0부터 시작한다고 가정한다.  
**해결:** GroupDocs.Annotation의 페이지 번호는 1부터 시작하므로 사용자에게 보이는 번호와 일치한다.

```java
// ```java
// 잘못된 예 - 페이지 0부터 시작하려고 함 (존재하지 않음)
saveOptions.setFirstPage(0);

// 올바른 예 - 실제 첫 페이지부터 시작
saveOptions.setFirstPage(1);

### 함정 #2: 리소스 누수
**문제:** `Annotator`를 닫지 않아 파일이 잠긴다.  
**해결:** 항상 `try with resources` 블록에 `Annotator`를 넣거나 `close()`를 명시적으로 호출한다.

```java
// ```java
// 좋은 예 - 자동 리소스 관리
try (final Annotator annotator = new Annotator(inputFile)) {
    // your code here
} // 자동으로 닫힘

// 수동 닫기 예시
Annotator annotator = null;
try {
    annotator = new Annotator(inputFile);
    // your code here
} finally {
    if (annotator != null) {
        annotator.dispose();
    }
}

### 함정 #3: 잘못된 페이지 범위
**문제:** 문서 페이지 수를 초과하는 범위를 지정한다.  
**해결:** 저장 전에 `annotator.getDocumentInfo().getPagesCount()`로 범위를 검증한다.

```java
// ```java
public void savePageRangeWithValidation(String inputFile, int firstPage, int lastPage) {
    try (final Annotator annotator = new Annotator(inputFile)) {
        // 문서 정보를 얻어 페이지 수 확인
        DocumentInfo documentInfo = annotator.getDocument().getDocumentInfo();
        int totalPages = documentInfo.getPageCount();
        
        // 범위 검증
        if (firstPage < 1 || firstPage > totalPages) {
            throw new IllegalArgumentException("First page out of range: " + firstPage);
        }
        if (lastPage < firstPage || lastPage > totalPages) {
            throw new IllegalArgumentException("Last page out of range: " + lastPage);
        }
        
        SaveOptions saveOptions = new SaveOptions();
        saveOptions.setFirstPage(firstPage);
        saveOptions.setLastPage(lastPage);
        
        String outputPath = new FilePathConfiguration().getOutputFilePath(inputFile);
        annotator.save(outputPath, saveOptions);
    }
}

## 성능 최적화 팁

### 대용량 문서 메모리 관리
100 페이지 이상의 PDF를 처리할 때는 주석이 있는 페이지만 로드하도록 설정해 힙 사용량을 낮춘다:

```java
// ```java
public class OptimizedPageRangeSaver {
    public void saveWithOptimization(String inputFile, int firstPage, int lastPage) {
        // 메모리 사용량을 낮추기 위한 설정
        LoadOptions loadOptions = new LoadOptions();
        loadOptions.setLoadOnlyAnnotatedPages(true); // 주석이 있는 페이지만 로드
        
        try (final Annotator annotator = new Annotator(inputFile, loadOptions)) {
            SaveOptions saveOptions = new SaveOptions();
            saveOptions.setFirstPage(firstPage);
            saveOptions.setLastPage(lastPage);
            
            // 선택 사항: 작은 출력 파일을 위해 압축 활성화
            saveOptions.setAnnotationsOnly(false); // 주석만 저장하려면 true로 설정
            
            String outputPath = new FilePathConfiguration().getOutputFilePath(inputFile);
            annotator.save(outputPath, saveOptions);
        }
    }
}

핵심 전략:
- `setLoadOnlyAnnotatedPages(true)`는 주석이 있는 페이지만 로드해 메모리 사용량을 감소시킨다.  
- `setAnnotationsOnly(true)`는 주석 레이어만 포함하는 경량 파일을 만든다.  
- 고정 스레드 풀을 이용한 배치 처리로 시스템 자원 고갈을 방지한다.

### 여러 문서 배치 처리
고처리량 시나리오에서는 파일을 배치로 처리한다:

```java
// ```java
public class BatchPageRangeSaver {
    public void processBatch(List<String> inputFiles, int firstPage, int lastPage) {
        for (String inputFile : inputFiles) {
            try {
                savePageRangeWithValidation(inputFile, firstPage, lastPage);
                System.out.println("Successfully processed: " + inputFile);
            } catch (Exception e) {
                System.err.println("Failed to process " + inputFile + ": " + e.getMessage());
                // 오류를 기록하고 다음 파일로 계속 진행
            }
        }
    }
}

## 인기 프레임워크와의 통합

### Spring Boot 문서 서비스 통합
아래는 PDF를 받아 페이지 범위를 추출하고 새 파일을 바이트 배열로 반환하는 최소형 Spring Boot 서비스이다.

```java
// ```java
@Service
public class DocumentPageRangeService {
    
    @Value("${app.document.output-directory}")
    private String outputDirectory;
    
    public String savePageRange(String inputFile, int firstPage, int lastPage) {
        try (final Annotator annotator = new Annotator(inputFile)) {
            SaveOptions saveOptions = new SaveOptions();
            saveOptions.setFirstPage(firstPage);
            saveOptions.setLastPage(lastPage);
            
            String outputPath = generateOutputPath(inputFile, firstPage, lastPage);
            annotator.save(outputPath, saveOptions);
            
            return outputPath;
        } catch (Exception e) {
            throw new DocumentProcessingException("Failed to save page range", e);
        }
    }
    
    private String generateOutputPath(String inputFile, int firstPage, int lastPage) {
        String baseName = FilenameUtils.getBaseName(inputFile);
        String extension = FilenameUtils.getExtension(inputFile);
        return String.format("%s/%s_pages_%d-%d.%s", 
                            outputDirectory, baseName, firstPage, lastPage, extension);
    }
}

서비스는 `AnnotatorFactory`에 대한 생성자 주입을 사용해 컨트롤러를 얇고 테스트 가능하게 만든다.

## 실용적인 적용 사례 및 사용 예시

### 법률 문서 처리
법무법인에서는 검토된 조항만 공유해야 할 때가 많다. 해당 페이지만 추출하면 기밀 섹션 노출 위험을 줄일 수 있다.

```java
// ```java
public class LegalDocumentProcessor {
    public void extractEvidencePages(String caseFile, List<Integer> evidencePages) {
        // 효율적인 처리를 위해 연속된 페이지를 그룹화
        List<PageRange> ranges = groupConsecutivePages(evidencePages);
        
        for (PageRange range : ranges) {
            String outputFile = String.format("evidence_%d_%d-to-%d.pdf", 
                                            getCaseNumber(caseFile), range.start, range.end);
            savePageRange(caseFile, range.start, range.end, outputFile);
        }
    }
}

### 교육 콘텐츠 관리
교사는 과제에 필요한 주석이 달린 챕터만 추출해 학생에게 제공함으로써 다운로드 용량을 줄이고 집중도를 높일 수 있다.

```java
// ```java
public class EducationalContentExtractor {
    public void createAssignmentPacket(String textbook, int chapterStart, int chapterEnd) {
        try (final Annotator annotator = new Annotator(textbook)) {
            SaveOptions saveOptions = new SaveOptions();
            saveOptions.setFirstPage(chapterStart);
            saveOptions.setLastPage(chapterEnd);
            
            String assignmentFile = generateAssignmentFileName(textbook, chapterStart, chapterEnd);
            annotator.save(assignmentFile, saveOptions);
        }
    }
}

### 품질 보증 검토
QA 팀은 리뷰어 코멘트가 있는 페이지만 분리해 빠른 반복 사이클을 구현한다.

```java
// ```java
public class QAReviewExtractor {
    public void extractReviewedPages(String document) {
        try (final Annotator annotator = new Annotator(document)) {
            // 주석이 있는 페이지 가져오기
            List<Integer> annotatedPages = getAnnotatedPageNumbers(annotator);
            
            if (!annotatedPages.isEmpty()) {
                int firstPage = Collections.min(annotatedPages);
                int lastPage = Collections.max(annotatedPages);
                
                SaveOptions saveOptions = new SaveOptions();
                saveOptions.setFirstPage(firstPage);
                saveOptions.setLastPage(lastPage);
                
                String reviewFile = document.replace(".pdf", "_review_comments.pdf");
                annotator.save(reviewFile, saveOptions);
            }
        }
    }
}

## 모범 사례 요약
1. **페이지 번호를 검증**한 후 저장 작업을 호출한다.  
2. **항상 `try with resources`**를 사용해 `Annotator`가 닫히도록 보장한다.  
3. **대용량 PDF**에는 `setLoadOnlyAnnotatedPages(true)`를 활성화해 메모리 사용량을 제어한다.  
4. **지원 포맷 전체를 테스트**한다—GroupDocs.Annotation은 PDF, DOCX, XLSX, PPTX 및 이미지 파일 등 50개 이상의 입력·출력 형식을 지원한다.  
5. **JVM 힙을 모니터링**하고 배치 작업에 맞게 `-Xmx` 옵션을 조정한다.  

## 일반적인 문제 해결

### 문제: “파일이 잠김” 오류
**증상:** `save()` 호출 시 파일이 잠겼다는 예외가 발생한다.  
**원인:**  
- 이전 `Annotator` 인스턴스를 닫지 않음.  
- 파일이 다른 애플리케이션에서 열려 있음.  
- 파일 시스템 권한 부족.  

**해결:** 모든 `Annotator`를 `try with resources`로 감싸고 OS 수준의 파일 잠금을 확인한다.

```java
// ```java
// 적절한 정리 보장
try (final Annotator annotator = new Annotator(inputFile)) {
    // ... your code ...
} // 자동으로 파일 핸들 해제

// 처리 전 파일 접근성 확인
File file = new File(inputFile);
if (!file.canRead()) {
    throw new IllegalArgumentException("Cannot read input file: " + inputFile);
}
if (!file.getParentFile().canWrite()) {
    throw new IllegalArgumentException("Cannot write to output directory");
}

### 문제: 메모리 부족 오류
**증상:** 대용량 PDF를 처리할 때 `OutOfMemoryError` 발생.  
**해결:**  
1. JVM 힙을 늘린다(`-Xmx2g` 이상).  
2. `setLoadOnlyAnnotatedPages(true)`와 `setAnnotationsOnly(true)`를 사용한다.  
3. 문서를 더 작은 배치로 나눠 처리한다.

### 문제: 주석이 보존되지 않음
**증상:** 출력 파일에 원본 마크업이 누락된다.  
**해결:** `setAnnotationsOnly(false)`가 기본값임을 확인하고, 실수로 `true`로 설정하지 않도록 한다.

```java
// ```java
SaveOptions saveOptions = new SaveOptions();
saveOptions.setAnnotationsOnly(false); // 콘텐츠와 주석 모두 유지
saveOptions.setFirstPage(firstPage);
saveOptions.setLastPage(lastPage);

## 자주 묻는 질문

**Q: 연속되지 않은 페이지(예: 1, 3, 7)를 저장할 수 있나요?**  
A: 단일 `SaveOptions` 호출로는 불가능하다. 각 구간을 별도로 저장한 뒤 결과를 병합한다.

**Q: 암호로 보호된 문서에도 작동하나요?**  
A: 예—`Annotator` 생성 시 비밀번호를 제공하면 된다: `new Annotator(inputFile, loadOptions.setPassword("your_password"))`.

**Q: 지원되는 파일 포맷은 무엇인가요?**  
A: PDF, Microsoft Word, Excel, PowerPoint 등 다수. 전체 목록은 [공식 문서](https://docs.groupdocs.com/annotation/java/)를 참고.

**Q: 원본 콘텐츠 없이 주석만 저장할 수 있나요?**  
A: 물론이다—`saveOptions.setAnnotationsOnly(true)`로 주석 전용 파일을 만든다.

**Q: 1000페이지 이상의 초대형 문서는 어떻게 처리하나요?**  
A: `setLoadOnlyAnnotatedPages(true)`를 사용하고, 청크 단위로 처리하며, 필요 시 JVM 힙을 증설한다.

**Q: 저장 전에 페이지를 미리 볼 수 있는 방법이 있나요?**  
A: GroupDocs.Annotation은 처리에 중점을 두지만, `annotator.getDocumentInfo()`를 통해 페이지 수와 주석 위치를 조회해 추출 범위를 결정할 수 있다.

## 추가 자료

- 문서: [GroupDocs.Annotation for Java Docs](https://docs.groupdocs.com/annotation/java/)  
- 공식 문서: [official documentation](https://docs.groupdocs.com/annotation/java/)  
- API 레퍼런스: [Complete API Documentation](https://reference.groupdocs.com/annotation/java/)  
- 다운로드: [Latest Releases](https://releases.groupdocs.com/annotation/java/)  
- GroupDocs 릴리스: [GroupDocs releases](https://releases.groupdocs.com/annotation/java/)  
- 라이선스 옵션: [License Options](https://purchase.groupdocs.com/buy)  
- 구매: [Purchase here](https://purchase.groupdocs.com/buy)  
- 무료 체험: [Try It Now](https://releases.groupdocs.com/annotation/java/)  
- 임시 라이선스: [Get Evaluation License](https://purchase.groupdocs.com/temporary-license/)  
- 지원: [Community Forum](https://forum.groupdocs.com/c/annotation/)  

---

**마지막 업데이트:** 2026-09-25  
**테스트 환경:** GroupDocs.Annotation 25.2 (Java)  
**작성자:** GroupDocs

## 관련 튜토리얼

- [Reduce PDF Size Java with GroupDocs.Annotation – Complete Guide](/annotation/java/document-saving/)  
- [Save Annotated PDF using GroupDocs Java & Azure Blob](/annotation/java/document-loading/download-annotate-azure-blob-groupdocs-java/)  
- [Load Password Protected PDF with GroupDocs.Annotation Java](/annotation/java/advanced-features/)

⛔ START YOUR RESPONSE WITH THE FIRST CHARACTER OF THE TRANSLATED CONTENT.