Skip to content

Commit 682b4bc

Browse files
fix: 커넥션 누수 처리(#295)
1 parent a363363 commit 682b4bc

File tree

2 files changed

+60
-40
lines changed

2 files changed

+60
-40
lines changed

src/main/java/PodoeMarket/podoemarket/product/controller/ProductController.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package PodoeMarket.podoemarket.product.controller;
22

3+
import PodoeMarket.podoemarket.common.entity.ProductEntity;
34
import PodoeMarket.podoemarket.common.entity.UserEntity;
45
import PodoeMarket.podoemarket.common.dto.ResponseDTO;
56
import PodoeMarket.podoemarket.product.dto.response.ScriptDetailResponseDTO;
67
import PodoeMarket.podoemarket.product.dto.response.ScriptListResponseDTO;
78
import PodoeMarket.podoemarket.common.entity.type.PlayType;
89
import PodoeMarket.podoemarket.product.service.ProductService;
10+
import PodoeMarket.podoemarket.service.S3Service;
911
import lombok.RequiredArgsConstructor;
1012
import lombok.extern.slf4j.Slf4j;
1113
import org.springframework.http.ResponseEntity;
@@ -22,6 +24,7 @@
2224
@RequestMapping("/scripts")
2325
public class ProductController {
2426
private final ProductService productService;
27+
private final S3Service s3Service;
2528

2629
@GetMapping
2730
public ResponseEntity<?> allProducts(@AuthenticationPrincipal UserEntity userInfo) {
@@ -76,7 +79,14 @@ public ResponseEntity<?> scriptInfo(@AuthenticationPrincipal UserEntity userInfo
7679
@GetMapping("/preview")
7780
public ResponseEntity<StreamingResponseBody> scriptPreview(@RequestParam("script") UUID productId) {
7881
try {
79-
return productService.generateScriptPreview(productId);
82+
// 데이터베이스 작업 (트랜잭션 내에서 수행)
83+
final ProductEntity product = productService.getProduct(productId);
84+
final String s3Key = product.getFilePath();
85+
final String preSignedURL = s3Service.generatePreSignedURL(s3Key);
86+
int pagesToExtract = (product.getPlayType() == PlayType.LONG) ? 3 : 1;
87+
88+
// PDF 처리 (트랜잭션 외부에서 수행)
89+
return productService.generateScriptPreview(preSignedURL, pagesToExtract);
8090
} catch(Exception e) {
8191
ResponseDTO resDTO = ResponseDTO.builder().error(e.getMessage()).build();
8292
return ResponseEntity.badRequest().body((StreamingResponseBody) resDTO);

src/main/java/PodoeMarket/podoemarket/product/service/ProductService.java

+49-39
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.itextpdf.kernel.pdf.PdfDocument;
1919
import com.itextpdf.kernel.pdf.PdfReader;
2020
import com.itextpdf.kernel.pdf.PdfWriter;
21+
import com.itextpdf.kernel.pdf.ReaderProperties;
2122
import org.springframework.http.MediaType;
2223
import org.springframework.http.ResponseEntity;
2324
import org.springframework.transaction.annotation.Transactional;
@@ -32,6 +33,7 @@
3233
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
3334

3435
import java.io.ByteArrayInputStream;
36+
import java.io.IOException;
3537
import java.io.InputStream;
3638
import java.io.UnsupportedEncodingException;
3739
import java.net.URI;
@@ -144,14 +146,9 @@ public ScriptDetailResponseDTO getScriptDetailInfo(UserEntity userInfo, UUID pro
144146

145147
}
146148

147-
public ResponseEntity<StreamingResponseBody> generateScriptPreview(UUID productId) {
149+
// 트랜잭션 없는 PDF 처리 메서드
150+
public ResponseEntity<StreamingResponseBody> generateScriptPreview(String preSignedURL, int pagesToExtract) {
148151
try {
149-
final ProductEntity product = getProduct(productId);
150-
final String s3Key = product.getFilePath();
151-
final String preSignedURL = s3Service.generatePreSignedURL(s3Key);
152-
153-
int pagesToExtract = (product.getPlayType() == PlayType.LONG) ? 3 : 1;
154-
155152
// PDF 처리는 트랜잭션과 분리
156153
PdfExtractionResult result = processPreviewPdf(preSignedURL, pagesToExtract);
157154

@@ -211,58 +208,71 @@ public PdfExtractionResult(int totalPageCount, byte[] extractedPdfBytes) {
211208
}
212209

213210
// 트랜잭션과 분리된 PDF 처리 메서드
214-
private PdfExtractionResult processPreviewPdf(String preSignedURL, int pagestoExtract) {
215-
try (InputStream fileStream = (new URI(preSignedURL).toURL().openStream())){
216-
return extractPagesFromPdf(fileStream, pagestoExtract);
211+
private PdfExtractionResult processPreviewPdf(String preSignedURL, int pagesToExtract) {
212+
InputStream fileStream = null;
213+
try {
214+
fileStream = new URI(preSignedURL).toURL().openStream();
215+
return extractPagesFromPdf(fileStream, pagesToExtract);
217216
} catch (Exception e) {
218217
throw new RuntimeException("PDF 처리 실패", e);
218+
} finally {
219+
if (fileStream != null) {
220+
try {
221+
fileStream.close();
222+
} catch (IOException e) {
223+
log.error("InputStream 닫기 실패: {}", e.getMessage());
224+
}
225+
}
219226
}
220227
}
221228

222229
// PDF의 특정 페이지까지 추출하는 함수
223230
private PdfExtractionResult extractPagesFromPdf(InputStream fileStream, int pagesToExtract) {
224-
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
225-
PdfReader reader = null;
226-
PdfWriter writer = null;
227-
PdfDocument originalDoc = null;
228-
PdfDocument newDoc = null;
231+
PdfReader reader = null;
232+
PdfWriter writer = null;
233+
PdfDocument originalDoc = null;
234+
PdfDocument newDoc = null;
235+
ByteArrayOutputStream outputStream = null;
229236

230-
try {
231-
reader = new PdfReader(fileStream);
232-
reader.setMemorySavingMode(true); // 메모리 절약 모드 활성화
237+
try {
238+
outputStream = new ByteArrayOutputStream();
233239

234-
writer = new PdfWriter(outputStream);
240+
// 안전 모드 설정 추가
241+
ReaderProperties properties = new ReaderProperties();
242+
reader = new PdfReader(fileStream, properties);
243+
reader.setMemorySavingMode(true); // 메모리 절약 모드 활성화
235244

236-
originalDoc = new PdfDocument(reader);
237-
newDoc = new PdfDocument(writer);
245+
writer = new PdfWriter(outputStream);
238246

239-
final int totalPageCount = originalDoc.getNumberOfPages();
240-
final int endPage = Math.min(pagesToExtract, totalPageCount);
247+
originalDoc = new PdfDocument(reader);
248+
newDoc = new PdfDocument(writer);
241249

242-
originalDoc.copyPagesTo(1, endPage, newDoc);
250+
final int totalPageCount = originalDoc.getNumberOfPages();
251+
final int endPage = Math.min(pagesToExtract, totalPageCount);
243252

244-
// 명시적으로 문서 닫기 (역순으로)
245-
newDoc.close();
246-
originalDoc.close();
247-
writer.close();
248-
reader.close();
253+
originalDoc.copyPagesTo(1, endPage, newDoc);
249254

250-
return new PdfExtractionResult(totalPageCount, outputStream.toByteArray());
251-
} catch (Exception e) {
252-
// 예외 발생 시에도 리소스 해제 보장 (역순으로)
253-
closeQuietly(newDoc, "newDoc");
254-
closeQuietly(originalDoc, "originalDoc");
255-
closeQuietly(writer, "writer");
256-
closeQuietly(reader, "reader");
255+
// 명시적으로 문서 닫기 (역순으로)
256+
newDoc.close();
257+
originalDoc.close();
258+
writer.close();
259+
reader.close();
257260

258-
throw new RuntimeException("PDF 페이지 추출 실패", e);
259-
}
261+
return new PdfExtractionResult(totalPageCount, outputStream.toByteArray());
260262
} catch (Exception e) {
261-
log.error("PDF 처리 중 오류 발생: error={}", e.getMessage());
263+
closeAllResources(newDoc, originalDoc, writer, reader, outputStream);
262264
throw new RuntimeException("PDF 처리 실패", e);
263265
}
264266
}
265267

268+
private void closeAllResources(PdfDocument newDoc, PdfDocument originalDoc, PdfWriter writer, PdfReader reader, ByteArrayOutputStream outputStream) {
269+
closeQuietly(newDoc, "newDoc");
270+
closeQuietly(originalDoc, "originalDoc");
271+
closeQuietly(writer, "writer");
272+
closeQuietly(reader, "reader");
273+
closeQuietly(outputStream, "outputStream");
274+
}
275+
266276
// 리소스를 안전하게 닫는 유틸리티 메서드
267277
private void closeQuietly(AutoCloseable resource, String resourceName) {
268278
if (resource != null) {

0 commit comments

Comments
 (0)