주석이 달린 문서에서 특정 페이지 저장하기 (Java)

소개

방대한 주석이 달린 문서 속에서 필요한 몇 페이지만 찾고 싶을 때가 있나요? try with resources java 를 사용하면 GroupDocs.Annotation을 이용해 필요한 페이지만 효율적으로 추출할 수 있습니다. 법률 계약서, 기술 매뉴얼, 연구 논문 등 어떤 종류의 문서를 다루든, 관련 페이지만 추출하면 저장 공간을 절약하고 처리 속도를 높이며 워크플로우를 깔끔하게 유지할 수 있습니다.

이 가이드에서는 라이브러리 설정부터 Java 애플리케이션을 원활하게 실행하도록 하는 고급 성능 팁까지 모든 과정을 단계별로 안내합니다.

학습 목표

  • Java 프로젝트에 GroupDocs.Annotation을 올바르게 설정하기
  • 깔끔하고 유지 보수 가능한 코드로 선택적 페이지 저장 구현하기
  • 대부분의 개발자가 흔히 겪는 실수를 피하기
  • 대용량 문서 처리 시 성능 최적화하기
  • 문제 발생을 사전에 방지하는 트러블슈팅 방법 익히기

빠른 답변

  • “try with resources java”는 무엇을 하나요? Annotator를 자동으로 닫아 파일 잠금 및 메모리 누수를 방지합니다.
  • 어떤 라이브러리가 페이지 범위 저장을 담당하나요? GroupDocs.AnnotationSaveOptionssetFirstPage/setLastPage 를 제공합니다.
  • Spring Boot 서비스에서도 사용할 수 있나요? 예 – “Spring Boot Document Service Integration” 섹션을 참고하세요.
  • 라이선스가 필요하나요? 개발 단계에서는 무료 체험판으로 충분하고, 운영 환경에서는 정식 라이선스가 필요합니다.
  • 대용량 PDF(1000페이지 이상)에서도 안전한가요? loadOnlyAnnotatedPages 와 배치 처리를 사용해 메모리 사용량을 낮출 수 있습니다.

왜 특정 페이지만 저장해야 할까? (실제 사례)

기술적인 내용에 들어가기 전에, 이 기능이 왜 중요한지 살펴보겠습니다:

저장 효율성: 500페이지 매뉴얼 중 주석이 달린 20페이지만 필요하다면, 전체 500페이지를 저장할 이유가 없습니다. 필요한 20페이지만 추출하면 파일 크기를 96 %까지 줄일 수 있습니다.

빠른 처리: 파일이 작아지면 업로드·다운로드·처리 속도가 빨라집니다. 사용자와 서버 모두에게 큰 도움이 됩니다.

향상된 사용자 경험: 수백 페이지를 스크롤하며 주석이 있는 부분을 찾을 필요가 없습니다. 필요한 부분만 제공하세요.

규정 준수 및 보안: 규제 산업에서는 문서의 특정 섹션만 공유하도록 요구될 수 있습니다. 선택적 저장은 이러한 규정을 쉽게 만족시켜 줍니다.

사전 요구 사항 및 설정

준비물

  • Java Development Kit (JDK): 버전 8 이상 (JDK 11+ 권장)
  • Maven 또는 Gradle: 의존성 관리용
  • GroupDocs.Annotation for Java: 버전 25.2 이상
  • 기본 Java 지식: 파일 I/O 및 OOP 이해

GroupDocs.Annotation for Java 설정하기

Maven 설정

다음 내용을 pom.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 사용자라면)

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

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

라이선스 준비하기

대부분의 튜토리얼이 알려주지 않는 팁: 무료 체험판부터 시작하세요. 복잡하게 생각할 필요 없습니다.

  • 무료 체험: 테스트 및 개발에 적합 – GroupDocs releases에서 받아보세요.
  • 임시 라이선스: 평가 기간을 더 늘리고 싶다면 임시 라이선스를 받으세요.
  • 정식 라이선스: 운영 환경에 배포하려면 여기서 구매하세요.

팁: 체험판에는 일부 제한이 있지만, 이 튜토리얼을 따라하고 개념 증명을 만들기에 충분합니다.

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

기본 접근법 (시작점)

가장 간단한 구현부터 시작합니다. 대부분(90 %)의 사용 사례에 적합합니다:

단계 1: 파일 경로 관리 설정

먼저 파일 경로를 처리하는 유틸리티 클래스를 만들어요 (디렉터리를 바꿔야 할 때 큰 도움이 됩니다):

import org.apache.commons.io.FilenameUtils;

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

왜 이렇게 할까요? 파일 경로 로직을 한 곳에 모아두면 테스트가 쉬워지고, FilenameUtils 를 사용해 원본 파일 확장자를 자동으로 유지할 수 있습니다.

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

마법이 일어나는 부분입니다:

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);  // Start from page 2
            saveOptions.setLastPage(4);   // End at page 4
            
            annotator.save(outputPath, saveOptions);
        }
    }
}

동작 설명

  • try‑with‑resources java 블록(try ( … ))을 사용해 Annotator 를 자동으로 닫아 파일 잠금 문제를 방지합니다.
  • setFirstPage(2)setLastPage(4) 로 포함 범위(2‑4페이지)를 정의합니다.
  • 범위는 양쪽 끝을 모두 포함한다는 점을 기억하세요 – 많은 개발자가 놓치는 부분입니다.

고급 파일 경로 구성

프로덕션 환경에서는 보다 유연한 경로 처리가 필요합니다:

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부터 시작합니다. 실제 문서와 동일하게 1이 첫 페이지입니다.

// Wrong - this tries to start from page 0 (doesn't exist)
saveOptions.setFirstPage(0);

// Right - this starts from the actual first page
saveOptions.setFirstPage(1);

실수 #2: 리소스 누수

문제: Annotator 를 적절히 닫지 않아 파일 잠금 및 메모리 누수가 발생함.

해결책: 항상 try‑with‑resources java 를 사용하거나 명시적으로 닫으세요:

// Good - automatic resource management
try (final Annotator annotator = new Annotator(inputFile)) {
    // your code here
} // automatically closes

// Also acceptable - manual closing
Annotator annotator = null;
try {
    annotator = new Annotator(inputFile);
    // your code here
} finally {
    if (annotator != null) {
        annotator.dispose();
    }
}

실수 #3: 잘못된 페이지 범위

문제: 문서에 존재하지 않는 페이지 범위를 지정함.

해결책: 먼저 범위를 검증하세요:

public void savePageRangeWithValidation(String inputFile, int firstPage, int lastPage) {
    try (final Annotator annotator = new Annotator(inputFile)) {
        // Get document info to check page count
        DocumentInfo documentInfo = annotator.getDocument().getDocumentInfo();
        int totalPages = documentInfo.getPageCount();
        
        // Validate range
        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 페이지 이상 대용량 문서를 다룰 때는 메모리 사용량이 중요합니다:

public class OptimizedPageRangeSaver {
    public void saveWithOptimization(String inputFile, int firstPage, int lastPage) {
        // Configure for lower memory usage
        LoadOptions loadOptions = new LoadOptions();
        loadOptions.setLoadOnlyAnnotatedPages(true); // Only load pages with annotations
        
        try (final Annotator annotator = new Annotator(inputFile, loadOptions)) {
            SaveOptions saveOptions = new SaveOptions();
            saveOptions.setFirstPage(firstPage);
            saveOptions.setLastPage(lastPage);
            
            // Optional: Enable compression for smaller output files
            saveOptions.setAnnotationsOnly(false); // Set to true if you only want annotations
            
            String outputPath = new FilePathConfiguration().getOutputFilePath(inputFile);
            annotator.save(outputPath, saveOptions);
        }
    }
}

핵심 최적화 전략

  • setLoadOnlyAnnotatedPages(true) 로 메모리 사용량을 감소시킵니다.
  • setAnnotationsOnly(true) 를 사용하면 주석 레이어만 포함한 가벼운 파일을 만들 수 있습니다.
  • 파일이 많다면 배치 처리(batch processing)를 적용하세요.

여러 문서 배치 처리

다수의 문서를 한 번에 처리해야 하는 프로덕션 시나리오:

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());
                // Log the error and continue with next file
            }
        }
    }
}

인기 프레임워크와의 통합

Spring Boot Document Service Integration

다음은 페이지 범위 저장을 위한 간단한 Spring Boot 서비스 예시입니다 (※ spring boot document service 라는 문구에 주목):

@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);
    }
}

실무 적용 사례

법률 문서 처리

법무법인에서는 계약서나 법원 문서의 특정 섹션만 추출해야 할 때가 많습니다:

public class LegalDocumentProcessor {
    public void extractEvidencePages(String caseFile, List<Integer> evidencePages) {
        // Group consecutive pages for efficient processing
        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);
        }
    }
}

교육 콘텐츠 관리

교사가 교재의 특정 장을 학생 과제용으로 추출할 때:

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);
        }
    }
}

품질 보증 검토

검토 의견이 달린 페이지만 추출해 집중적인 수정 작업을 수행할 때:

public class QAReviewExtractor {
    public void extractReviewedPages(String document) {
        try (final Annotator annotator = new Annotator(document)) {
            // Get pages with annotations
            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 java 사용 – 리소스 누수와 파일 잠금 문제를 방지합니다.
  3. 적절한 오류 처리 구현 – 하나의 잘못된 파일이 전체 배치를 중단하지 않도록 합니다.
  4. 메모리 사용량 고려 – 대용량 문서에는 setLoadOnlyAnnotatedPages(true) 를 활용하세요.
  5. 다양한 파일 형식 테스트 – PDF, Word, PowerPoint 등은 동작 방식이 다를 수 있습니다.
  6. 성능 모니터링 – 프로덕션에서 처리 시간과 메모리 사용량을 지속적으로 확인하세요.

일반적인 문제 해결

문제: “File is locked” 오류

증상: 저장 시 파일 잠금 관련 예외 발생.

원인

  • 이전 작업에서 Annotator 가 제대로 닫히지 않음.
  • 다른 애플리케이션에서 파일을 열어 둠.
  • 권한 부족.

해결책:

// Ensure proper cleanup
try (final Annotator annotator = new Annotator(inputFile)) {
    // ... your code ...
} // Automatically releases file handles

// Verify file accessibility before processing
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");
}

문제: 메모리 부족 오류

증상: 대용량 문서 처리 중 OutOfMemoryError 발생.

해결책

  1. JVM 힙 크기를 늘리기 (-Xmx2g 등).
  2. 앞서 소개한 최적화 옵션 사용.
  3. 문서를 더 작은 배치로 나누어 처리.

문제: 주석이 보존되지 않음

증상: 출력 파일에 원본 주석이 포함되지 않음.

해결책: 주석을 제거하는 옵션을 사용하지 않았는지 확인하세요:

SaveOptions saveOptions = new SaveOptions();
saveOptions.setAnnotationsOnly(false); // Keep both content and annotations
saveOptions.setFirstPage(firstPage);
saveOptions.setLastPage(lastPage);

자주 묻는 질문

Q: 연속되지 않은 페이지(예: 1, 3, 7)를 저장할 수 있나요?
A: 단일 작업으로는 직접 지원되지 않습니다. 각 범위별로 별도 저장을 수행하거나 결과를 나중에 합쳐야 합니다.

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

Q: 지원되는 파일 형식은 무엇인가요?
A: PDF, Microsoft Word, Excel, PowerPoint 등 다양한 형식을 지원합니다. 전체 목록은 공식 문서를 확인하세요.

Q: 원본 내용 없이 주석만 저장할 수 있나요?
A: 가능합니다. saveOptions.setAnnotationsOnly(true) 로 주석 전용 파일을 만들 수 있습니다.

Q: 1000페이지 이상 초대형 문서는 어떻게 처리하나요?
A: setLoadOnlyAnnotatedPages(true) 를 사용하고, 문서를 청크 단위로 나누어 처리하며, 필요에 따라 JVM 힙을 확대하세요.

Q: 저장 전에 페이지를 미리볼 수 있나요?
A: GroupDocs.Annotation은 주로 처리에 초점을 맞추므로 직접적인 미리보기 기능은 제공되지 않지만, 문서 정보(페이지 수, 주석 위치 등)를 조회해 추출 범위를 결정할 수 있습니다.

리소스


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