devkobe24.com
AWS
Algorithm
2024
Architecture
Archive
AWS_archive
CPP_DS
CS_archive
DataStructure
Database
HackTheSwift
Java_archive
Leet-Code
MySQL
Network_archive
OS
Post
Read English Book
SQL_archive
Spring & Spring Boots
TIL
Web
Backend Development
CS
2024
2025
Code Review
DB
Data Structure
Development tools and environments
Interview
Java
Java多識
Java
Math
Network
2024
Others
SQL
2024
Server
Spring
Troubleshooting
Home
Contact
Copyright © 2024 |
Yankos
Home
> Backend Development
Now Loading ...
Backend Development
📚[Backend Development] 무한 depth 댓글 정렬 구조란 무엇일까요?
“📚[Backend Development] 무한 depth 댓글 정렬 구조란 무엇일까요?” ✅ 무한 Depth 댓글 정렬 구조 무한 depth 댓글을 페이징 처리하기 위해서는 트리 구조를 유지하면서 정렬하는 전략이 필요합니다. 보통 parent_comment_id + comment_id 정렬 방식을 사용하는 2-depth 댓글과는 달리, 무한 depth의 경우 댓글의 계층 구조를 유지할 수 있도록 정렬 방식이 개선되어야 합니다. 🚀1️⃣ 트리 구조 기반 정렬 방법 무한 depth의 댓글을 정렬하려면 다음과 같은 방식이 가능합니다. 1️⃣ 정렬 방식 root_comment_id (최상위 부모 ID) 오름차순 path (트리 순서) 오름차순 comment_id (작성 순서) 오름차순 이렇게 정렬하면 트리 구조를 유지하면서 댓글을 시간순으로 정렬할 수 있습니다. 🚀2️⃣ 트리 구조를 유지하는 정렬 필드 필드명 설명 comment_id 댓글의 고유 ID (기본 키) parent_comment_id 부모 댓글의 ID (최상위 댓글이면 NULL) root_comment_id 최상위 부모 댓글의 ID (최상위 댓글이면 자기 자신 comment_id) depth 댓글의 깊이 (0부터 시작) path 트리 구조를 나타내는 정렬용 문자열 🚀3️⃣ 정렬 순서 SQL 예제 📌 무한 Depth 정렬을 위한 ORDER BY SELECT * FROM comment WHERE article_id = ? ORDER BY root_comment_id ASC, path ASC, comment_id ASC LIMIT ?, ?; 📌 정렬 기준. 1. root_comment_id ASC ➞ 최상위 부모 댓글 기준으로 정렬 2. path ASC ➞ 트리 구조를 유지하면서 정렬 3. comment_id ASC ➞ 같은 depth 내에서 작성 순서대로 정렬 🚀4️⃣ path 필드란? 트리 구조를 표현하기 위해 path 필드를 활용할 수 있습니다. path는 부모-자식 관계를 명확하게 하여 정렬을 용이하게 합니다. 예를 들어, path는 다음과 같은 방식으로 저장될 수 있습니다. comment_id parent_comment_id root_comment_id depth path 1 NULL 1 0 00001 2 1 1 1 00001.00002 3 1 1 1 00001.00003 4 2 1 2 00001.00002.00004 5 4 1 3 00001.00002.00004.00005 📌 정렬 시 ORDER BY path ASC를 사용하면 계층 구조를 유지하면서 정렬 가능! 🚀5️⃣ 정리 ✅ 무한 depth 댓글 정렬을 위헤 path 또는 lft/rgt 방식이 필요 ✅ 정렬 순서는 root_comment_id ASC, path ASC, comment_id ASC 방식 사용 ✅ 페이징 처리 시 LIMIT ?,? 적용 가능 📌 기존 2-depth 방식처럼 parent_comment_id 정렬만으로는 무한 depth 정렬이 어려우므로 path를 활용하는 것이 가장 적절합니다.
Backend Development
· 2025-02-20
📚[Backend Development] 무한 Depth 댓글 정렬 구조의 'Path Enumeration(경로 열거) 방식'이란 무엇일까요?
“📚[Backend Development] 무한 Depth 댓글 정렬 구조의 ‘Path Enumeration(경로 열거) 방식’이란 무엇일까요?” ✅ 무한 Depth 댓글 정렬 구조의 “Path Enumeration(경로 열거) 방식” 설명. Path Enumeration(경로 열거) 방식은 트리 구조의 계층을 문자열 형태로 저장하여 정렬 및 검색을 효율적으로 수행하는 방식입니다. 이 방식은 트리의 부모-자식 관계를 유지하면서 빠르게 정렬 및 조회할 수 있도록 도와줍니다. 🏗️1️⃣ Path Enumeration(경로 열거) 방식이란? Path Enumeration(경로 열거) 방식에서는 각 댓글의 부모-자식 관계를 문자열 경로(path)로 저장합니다. 즉, 각 댓글이 트리 구조에서 어떤 위치에 있는지 경로를 미리 기록하여 정렬 및 검색을 최적화합니다. 🏗️2️⃣ 테이블 구조. Path Enumeration(경로 열거) 방식을 사용하면 다음과 같은 추가적인 path 컬럼이 필요합니다. 필드명 설명 comment_id 댓글의 고유 ID (PK) parent_comment_id 부모 댓글의 ID (최상위 댓글이면 NULL) article_id 해당 댓글이 속한 게시글 ID content 댓글 내용 created_at 댓글 작성 시간 path 댓글의 계층 구조를 나타내는 문자열 (예: “00001.00002.00005” 📌 path 필드는 각 댓글이 트리 구조에서 어디에 속하는지 나타냄 📌 이 값을 활용하면 부모-자식 관계를 정렬 및 조회하는 것이 쉬워짐 🏗️3️⃣ Path 값 저장 방식. path 값은 댓글이 트리에서 어떤 위치에 있는지를 나타냅니다. 각 comment_id를 5자리 문자열(00001, 00002 등)로 변환하여 부모-자식 관계를 저장합니다. 📌 Path 값 예시 comment_id parent_comment_id path 1 NULL 00001 2 1 00001.00002 3 NULL 00003 4 2 00001.00002.00004 5 4 00001.00002.00004.00005 6 NULL 00006 📌 각 댓글은 부모 path를 상속받고, 자신의 ID를 추가하여 path를 생성 📌 부모 댓글이 삭제되더라도 path를 통해 계층 구조를 쉽게 유지 가능 🏗️4️⃣ Path Enumeration(경로 열거)을 활용한 정렬. Path Enumeration(경로 열거)을 활용하면 경로 순서대로 정렬하면 댓글을 계층 구조 그대로 유지할 수 있습니다. SELECT * FROM comment WHERE article_id = ? ORDER BY path ASC; 📌 ORDER BY path ASC를 적용하면 트리 구조를 유지하면서 정렬됨 📌 일반적인 ORDER BY parent_comment_id, comment_id보다 트리 구조 정렬이 정확함 🏗️5️⃣ Path Enumeration(경로 열거) 방식으로 조회 📌 예제 데이터 comment_id parent_comment_id path 1 NULL 00001 2 1 00001.00002 3 NULL 00003 4 2 00001.00002.00004 5 4 000001.00002.00004.00005 6 NULL 00006 📌 정렬된 결과. SELECT * FROM comment WHERE article_id = ? ORDER BY path ASC; ✅ 출력 결과 1. 댓글1 ├── 댓글2 ├── 댓글4 ├── 댓글5 2. 댓글3 3. 댓글6 📌 계층 구조가 정확하게 유지되면서 정렬됨. 🏗️6️⃣ 특정 댓글의 하위 댓글 조회. Path Enumeration(경로 열거) 방식에서는 특정 댓글의 모든 하위 댓글을 손쉽게 조회할 수 있습니다. SELECT * FROM comment WHERE path LIKE '00001.00002%' ORDER BY path ASC; 📌 결과: 00001.00002로 시작하는 모든 하위 댓글을 조회 (댓글2, 댓글4, 댓글5 포함) 🏗️7️⃣ Path Enumeration 방식의 장점과 단점. ✅ 장점. 장점 설명 트리 구조 유지가 쉬움 ORDER BY path ASC만으로 계층 구조 정렬 가능 하위 댓글 조회가 빠름 LIKE ‘경로%’로 손쉽게 하위 댓글 조회 가능 부모 댓글 삭제 시 계층 구조 유지 가능 path를 통해 상위 댓글을 식별 가능 ❌ 단점. 단점 설명 댓글 이동 시 path 업데이트 필요 댓글을 다른 부모로 이동하면 path를 변경해야 함 path 길이 증가 가능성 댓글이 깊어질수록 path 길이가 길어질 수 있음 INSERT 성능 저하 가능성 새로운 댓글 추가 시 path를 계산해야 함 🚀8️⃣ 정리. ✅ Path Enumeration(경로 열거) 방식은 댓글의 계층 구조를 문자열(path)로 저장하는 방식 ✅ ORDER BY path ASC를 사용하여 트리 구조를 유지하면서 정렬 가능 ✅ 하위 댓글 조회 시 LIKE ‘경로%’를 활용하여 빠르게 검색 가능 ✅ 부모 댓글이 삭제되더라도 계층 구조를 유지하는 데 유리 ✅ 댓글 이동이 빈번한 경우 path 업데이트가 필요하므로 조심해야 함 📌 무한 Depth 댓글 정렬 및 조회 성능을 최적화할 수 있는 가장 효과적인 방법 중 하나입니다.
Backend Development
· 2025-02-20
📚[Backend Development] 최대 2 Depth 댓글 정렬 구조의 '페이지 번호 방식'이란 무엇일까요?
“📚[Backend Development] 최대 2 Depth 댓글 정렬 구조의 ‘페이지 번호 방식’이란 무엇일까요?” ✅ 최대 2 Depth 댓글 정렬 구조의 “페이지 번호 방식” 설명. 최대 2 Depth 댓글을 페이지 번호 기반으로 조회하는 방식은 고정된 개수의 댓글을 불러오는 전통적인 페이징 방식입니다. 이를 통해 오래된 댓글부터 순서대로 불러올 수 있습니다. 🏗️1️⃣ 페이지 번호 방식이란? 페이지 번호 방식은 특정 페이지의 댓글을 불러오기 위해 OFFSET과 LIMIT을 활용하는 방식입니다. SELECT * FROM comment WHERE article_id = ? ORDER BY parent_comment_id ASC, comment_id ASC LIMIT ?, ?; SQL 키워드 설명 ORDER BY parent_comment_by ASC, comment_id ASC 댓글을 부모-자식 관계에 맞게 정렬 LIMIT ?, ? 몇 개의 데이터를 가져올지 지정 OFFSET 특정 페이지의 댓글을 건너뛴 후 가져옴 🏗️2️⃣ 정렬 방식 페이지 번호 방식에서는 댓글을 부모-자식 관계를 유지하면서 정렬해야 합니다. 정렬 기준은 다음과 같습니다. ORDER BY parent_comment_id ASC, comment_id ASC 최상위 댓글을 먼저 정렬 ➞ parent_comment_id IS NULL 순서대로 정렬 대댓글은 같은 부모 아래에서 정렬 ➞ comment_id ASC 순서로 정렬 페이징 처리 ➞ LIMIT ?, ? 사용 🏗️3️⃣ SQL 예제 (페이지 번호 기반 조회) 예를 들어, 한 페이지당 3개 댓글을 가져오도록 설정하고, 2번째 페이지(page = 2)를 조회한다고 가정해보겠습니다. SELECT * FROM comment WHERE article_id = ? ORDER BY parent_comment_id ASC, comment_id ASC LIMIT 3 OFFSET 3; 📌 페이지 번호 공식. OFFSET = (page - 1) * pageSize page = 1 ➞ OFFSET = (1-1) * 3 = 0 (첫 번째 페이지) page = 2 ➞ OFFSET = (2-1) * 3 = 3 (두 번째 페이지) page = 3 ➞ OFFSET = (3-1) * 3 = 6 (세 번째 페이지) 🏗️4️⃣ 데이터 예시 📌 데이터베이스에 저장된 댓글 데이터. comment_id parent_comment_id 내용 1 NULL 댓글1 (최상위 댓글) 2 1 댓글2 (댓글1의 대댓글) 3 NULL 댓글3 (최상위 댓글) 4 1 댓글4 (댓글1의 대댓글) 5 3 댓글5 (댓글3의 대댓글) 6 NULL 댓글6 (최상위 댓글) 📌 페이지 번호 기반 조회 결과 (한 페이지에 3개씩) ✅ 1페이지 조회 (page = 1, pageSize = 3) SELECT * FROM comment WHERE article_id = ? ORDER BY parent_comment_id ASC, comment_id ASC LIMIT 3 OFFSET 0; comment_id parent_comment_id 내용 1 NULL 댓글1 (최상위 댓글) 2 1 댓글2 (댓글1의 대댓글) 4 1 댓글4 (댓글1의 대댓글) ✅ 2페이지 조회 (page = 2, pageSize = 3) SELECT * FROM comment WHERE article_id = ? ORDER BY parent_comment_id ASC, comment_id ASC LIMIT 3 OFFSET 3; comment_id parent_comment_id 내용 3 NULL 댓글3 (최상위 댓글) 5 3 댓글5 (댓글3의 대댓글) 6 NULL 댓글6 (최상위 댓글) 📌 페이지를 넘길 때마다 다음 pageSize 만큼의 데이터를 가져옵니다. 🏗️5️⃣ 장점과 단점. 장점 단점 간단하고 직관적인 페이징 구현 가능 페이지 번호가 커질수록 OFFSET이 증가하여 성능 저하 댓글을 정렬된 순서대로 가져올 수 있음 대량의 데이터에서 OFFSET이 클 경우 속도가 느려질 수 있음 📌 대체 방법 OFFSET이 큰 경우 “Keyset Pagination (무한스크롤 방식)”을 사용하는 것이 더 효율적일 수 있음 ORDER BY parent_comment_id ASC, comment_id ASC 정렬을 유지하면서 WHERE comment_id > ? 방식을 활용하는 방식도 있음 🚀6️⃣ 정리. ✅ 페이지 번호 기반 댓글 조회는 LIMIT ?, OFFSET ?을 사용 ✅ ORDER BY parent_comment_id ASC, comment_id ASC를 사용해 계층 구조 유지 ✅ OFFSET 값이 클 경우 성능 저하 가능 ➞ Keyset Pagination 고려 가능 ✅ 최대 2 Depth 댓글 구조에서는 성능 이슈가 적고 직관적인 방식으로 구현 가능 📌 최대 2 Depth 댓글 구조에서는 페이지 번호 방식이 효율적이며, 오래된 댓글부터 순서대로 불러오기에 적합합니다.
Backend Development
· 2025-02-20
📚[Backend Development] 최대 2 Depth 댓글 정렬 구조의 '무한 스크롤 방식'이란 무엇일까요?
“📚[Backend Development] 최대 2 Depth 댓글 정렬 구조의 ‘무한 스크롤 방식’이란 무엇일까요?” ✅ 최대 2 Depth 댓글 정렬 구조의 “무한 스크롤 방식” 설명. 무한 스크롤 방식은 페이지 번호 방식(LIMIT ?, OFFSET ?)을 사용하지 않고, 마지막으로 불러온 댓글의 ID를 기준으로 다음 댓글을 불러오는 방식(Keyset Pagination)입니다. 이 방식은 페이지 번호 방식보다 성능이 우수하여, 대량의 데이터를 빠르게 로드할 수 있습니다. 🏗️1️⃣ 무한 스크롤 방식이란? 무한 스크롤 방식은 마지막으로 불러온 댓글(lastCommentId)을 기준으로 그 이후 데이터를 가져오는 방식입니다. SELECT * FROM comment WHERE article_id = ? AND comment_id > ? ORDER BY parent_comment_id ASC, comment_id ASC LIMIT ?; SQL 키워드 설명 WHERE comment_id > ? 마지막 댓글 ID 이후 데이터만 가져옴 ORDER BY parent_comment_id ASC, comment_id ASC 부모-자식 관계를 유지하면서 정렬 LIMIT ? 한 번에 가져올 최대 개수 지정 📌 이 방식을 사용하면 OFFSET을 사용하지 않기 때문에 성능이 훨씬 우수합니다. 🏗️2️⃣ SQL 예제 (무한 스크롤 방식) 예를 들어, 한 번에 3개의 댓글을 불러오도록 설정하고, 마지막으로 불러온 댓글의 ID(lastCommentId)가 3이라고 가정합니다. SELECT * FROM comment WHERE article_id = ? AND comment_id > 3 ORDER BY parent_comment_id ASC, comment_id ASC LIMIT 3; 🏗️3️⃣ 정렬 방식 📌 정렬 기준. ORDER BY parent_comment_id ASC, comment_id ASC 최상위 댓글을 먼저 정렬 ➞ parent_comment_id IS NULL 순서대로 정렬 대댓글은 같은 부모 아래에서 정렬 ➞ comment_id ASC 순서로 정렬 페이징 없이 WHERE comment_id > lastCommentId 방식으로 조회 🏗️4️⃣ 데이터 예시. 📌 데이터베이스에 저장된 댓글 데이터 comment_id parent_comment_id 내용 1 NULL 댓글1 (최상위 댓글) 2 1 댓글2 (댓글1의 대댓글) 3 NULL 댓글3 (최상위 댓글) 4 1 댓글4 (댓글1의 대댓글) 5 3 댓글5 (댓글3의 대댓글) 6 NULL 댓글6 (최상위 댓글) 📌 무한 스크롤 방식으로 데이터 조회. ✅ 첫 번째 요청(lastCommentId = 0) SELECT * FROM comment WHERE article_id = ? AND comment_id > 0 ORDER BY parent_comment_id ASC, comment_id ASC LIMIT 3; 📌 결과 comment_id parent_comment_id 내용 1 NULL 댓글1 (최상위 댓글) 2 1 댓글2 (댓글1의 대댓글) 4 1 댓글4 (댓글1의 대댓글) 📌 마지막 댓글 ID = 4 ✅ 두 번째 요청(lastCommentId = 4) SELECT * FROM comment WHERE article_id = ? AND comment_id > 4 ORDER BY parent_comment_id ASC, comment_id ASC LIMIT 3; 📌 결과 comment_id parent_comment_id 내용 3 NULL 댓글3 (최상위 댓글) 5 3 댓글5 (댓글3의 대댓글) 6 NULL 댓글6 (최상위 댓글) 📌 마지막 댓글 ID = 6 🏗️5️⃣ 장점과 단점. 장점 단점 OFFSET 없이 빠른 조회 가능 (성능 최적화) lastCommentId를 클라이언트가 유지해야 함 대량의 댓글이 있는 경우 효율적 댓글이 삭제될 경우 정렬이 흐트러질 가능성이 있음 페이지 번호 방식보다 확장성이 좋음 정렬 순서가 유지되도록 조심해야 함 🚀6️⃣ 정리. ✅ 무한 스크롤 방식은 WHERE comment_id > lastCommentId를 사용하여 데이터 조회 ✅ ORDER BY parent_comment_id ASC, comment_id ASC를 사용해 계층 구조 유지 ✅ 페이지 번호 방식(LIMIT ?, OFFSET ?)보다 성능이 우수하며 대량 데이터 처리에 적합 ✅ 마지막 댓글 ID(lastCommentId)를 유지해야 함 📌 최대 2 Depth 댓글 구조에서는 무한 스크롤 방식이 성능 최적화에 유리하며, 빠르게 댓글을 불러올 수 있습니다.
Backend Development
· 2025-02-20
📚[Backend Development] 최대 2depth 댓글 정렬 구조란 무엇일까요?
“📚[Backend Development] 최대 2depth 댓글 정렬 구조란 무엇일까요?” ✅ 최대 2 Depth 댓글 정렬 구조 설명. 최대 2 Depth(계층이 최대 2단계)까지만 허용하는 댓글 시스템의 정렬 구조는 비교적 단순하면서도 효율적입니다. 🏗️1️⃣ 댓글 테이블 구조. 최대 2 Depth 댓글을 저장하는 테이블 구조는 다음과 같습니다. 필드명 설명 comment_id 댓글의 고유 ID (PK) parent_comment_id 부모 댓글 ID (최상위 댓글이면 NULL) article_id 해당 댓글이 속한 게시글 ID content 댓글 내용 created_at 댓글 작성 시간 📌 특징. 최상위 댓글은 parent_comment_id = NULL (예: 댓글 1, 댓글 3) 자식 댓글은 parent_comment_id = 부모의 comment_id (예: 댓글2, 댓글 4, 댓글 5) 2 Depth까지만 허용 (댓글의 댓글까지만 가능, 대댓글의 대댓글은 불가능) 🏗️2️⃣ 정렬 방식 최대 2 Depth 댓글 정렬은 다음과 같은 순서로 진행됩니다. ORDER BY parent_comment_id ASC, comment_id ASC 📌 정렬 기준. parent_comment_id ASC ➞ 같은 부모 댓글을 기준으로 그룹화. comment_id ASC ➞ 작성된 순서대로 정렬 (오래된 댓글이 먼저 출력됨). 🏗️3️⃣ 정렬 데이터 예시. 위 정렬 방식에 따라 댓글 데이터를 조회하면 다음과 같은 형태가 됩니다. parent_comment_id comment_id 내용 NULL 1 댓글1 (최상위 댓글) 1 2 댓글2 (댓글1의 대댓글) 1 4 댓글4 (댓글 1의 대댓글) NULL 3 댓글3 (최상위 댓글) 3 5 댓글5 (댓글 3의 대댓글) 📌 이 정렬 방식의 장점. parent_comment_id를 기준으로 먼저 정렬하여 최상위 댓글이 먼저 출력됨 같은 parent_comment_id를 가진 댓글(대댓글)은 작성 순서대로 정렬됨 LIMIT ?, ?을 활용하여 페이징 처리 가능 🏗️4️⃣ SQL 정렬 예제 SELECT * FROM comment WHERE article_id = ? ORDER BY parent_comment_id ASC, comment_id ASC LIMIT ?, ?; 🏗️5️⃣ 페이징 처리. 각 페이지에서 N개 댓글을 불러올 수 있도록 LIMIT ?,? 사용 최상위 댓글과 대댓글을 함께 불러오기 위해 parent_comment_id 기준 정렬 유지 최대 depth가 2이므로 성능 최적화에 유리함 ✅6️⃣ 정리. ✅ 최대 2 Depth 구조 ➞ parent_comment_id를 활용해 부모-자식 관계 유지 ✅ 정렬 순서 ➞ ORDER BY parent_comment_id ASC, comment_id ASC ✅ 조회 결과 ➞ 부모 댓글이 먼저, 대댓글이 뒤에 정렬됨 ✅ 페이징 가능 ➞ LIMIT ?, ?를 활용하여 오래된 순으로 페이징 처리
Backend Development
· 2025-02-19
📚[Backend Development] mappedBy란 무엇일까요?
“📚[Backend Development] mappedBy란 무엇일까요?” 🍎 Intro. mappedBy는 양방향 연관관계에서 사용되는 속성으로, 연관 관계의 주인이 아닌(읽기 전용) 쪽에서 사용합니다. 즉, 외래 키(FK)를 관리하지 않는 쪽에서 mappedBy를 사용하여 연관 관계를 매핑합니다. ✅1️⃣ mappedBy의 필요성. 양방향 관계에서는 두 개의 엔티티가 서로를 참조하게 되는데, JPA는 외래 키(FK)를 관리할 “주인”을 하나만 지정해야 합니다. 이때, 연관 관계의 주인이 아닌 쪽에서 mappedBy를 사용하여 주인을 명시합니다. ✅2️⃣ @OneToOne 양방향 관계에서 mappedBy 사용 예제 1️⃣ User 엔티티 (연관 관계의 주인) @Entity public class User { @Id @GeneratedValue(strategy = Generation.IDENTITY) private Long id; private String username; @OneToOne @JoinColumn(name = "profile_id") // FK를 관리하는 주인 (user 테이블에 profile_id FK 생성) private UserProfile profile; // Getter, Setter } 2️⃣ UserProfile 엔티티(mappedBy 사용) @Entity public class UserProfile { @Id @GenerationValue(strategy = Generation.IDENTITY) private Long id; private String bio; private String website; @OneToOne(mappedBy = "profile") // User 엔티티의 profile 필드가 관계의 주인 private User user; // Getter, Setter } ✅3️⃣ mappedBy = “profile”의 의미 “profile”은 User 엔티티의 profile 필드명을 가리킵니다. 즉, 이 관계의 주인은 User.profile이며, UserProfile 엔티티는 읽기 전용입니다. 따라서 UserProfile.user 필드는 외래 키(FK)를 생성하지 않고, 매핑만 수행합니다. ✅4️⃣ 데이터베이스 테이블 구조 위 코드를 실행하면 user 테이블만 profile_id라는 FK 컬럼을 가지며, user_profile 테이블에는 추가 컬럼이 생성되지 않습니다. 📊 user 테이블 id username profile_id (FK) 1 Alice 101 2 Bob 102 📊 user_profile 테이블 id bio website 101 “Gamer” “alice.com” 102 “Developer” “bob.dev” 📌 외래 키는 user.profile_id에만 존재하며, user_profile 테이블에는 FK 컬럼이 없습니다. ✅5️⃣ mappedBy를 사용한 데이터 조회 ✅ User ➞ UserProfile 조회(가능 ✅) User user = entityManager.find(User.class, 1L); UserProfile profile = user.getProfile(); // 정상 작동 ✅ UserProfile ➞ User 조회(가능 ✅) UserProfile profile = entityManager.find(UserProfile.class, 101L); User user = profile.getUser(); // mappedBy를 사용했으므로 가능! 🚀 정리. ✔️ 연관 관계의 주인(Owner)이 아닌 쪽에서 mappedBy를 사용해야 한다. ✔️ “mappedBy = 주인 엔티티 필드명”으로 설정해야 한다. ✔️ 외래 키(FK)는 mappedBy를 사용한 쪽이 아니라 주인이 관리한다. ✔️ mappedBy는 읽기 전용이므로 @JoinColumn을 사용하지 않는다. 📌 mappedBy를 사용하면 불필요한 FK 컬럼 생성 방지 및 데이터베이스 테이블을 깔끔하게 유지할 수 있습니다.
Backend Development
· 2025-02-18
📚[Backend Development] @OneToMany란 무엇일까요?
“📚[Backend Development] @OneToMany란 무엇일까요?” 🍎 Intro. @OneToMany는 일대다(1:N) 관계를 매핑할 때 사용하는 어노테이션입니다. 즉, 하나(One)의 엔티티가 여러 개(Many)의 엔티티를 참조하는 구조입니다. ✅1️⃣ @OneToMany 예제. 게시글(Article)과 댓글(Comment) 관계를 예로 들어보겠습니다. 하나의 게시글(Article)에는 여러 개의 댓글(Comment)이 달릴 수 있습니다. 1️⃣ Article 엔티티(게시글) @Entity public class Article { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private String content; @OneToMany(mappedBy = "article") // Comment 엔티티의 article 필드가 관계의 주인 private List<Comment> comments = new ArrayList<>(); // Getter, Setter } 2️⃣ Comment 엔티티(댓글) @Entity public class Comment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String content; @ManyToOne @JoinColumn(name = "article_id") // comment 테이블에 article_id FK 생성 private Article article; // Getter, Setter } ✅2️⃣ @OneToMany(mappedBy = “article”)의 의미 Comment 엔티티의 article 필드를 참조하여 양방향 관계를 설정합니다. 외래 키를 관리하는 주인은 Comment.article 필드이며, Article 엔티티는 mappedBy를 통해 읽기 전용입니다. 즉, Comment 엔티티가 관계의 주인이고, Article 엔티티에서는 직접 FK를 관리하지 않습니다. (➞ @JoinColumn이 Comment 쪽에만 있는 이유) ✅3️⃣ 데이터베이스 테이블 구조. 위 코드를 실행하면 다음과 같은 테이블이 생성됩니다. 📌 article 테이블 (게시글) id title content 1 “Hello JPA” “JPA 배우기” 2 “Spring Boot” “Spring 공부” 📌 comment 테이블 (게시글에 연결된 댓글, article_id FK 포함) id content article_id(FK) 1 “좋은 글이네요!” 1 2 “유익한 정보 감사합니다.” 1 3 “Spring 최고!” 2 📌 article_id 컬럼이 게시글(Article)을 참조하는 외래 키(FK)입니다. 즉, 하나의 Article에는 여러 개의 Comment가 연결될 수 있습니다. ✅4️⃣ 데이터 조회. ✅ 특정 게시글에 속한 댓글 가져오기. 양방향 관계가 설정되어 있으므로, 특정 게시글에 달린 댓글을 쉽게 가져올 수 있습니다. Article article = entityManager.find(Article.class, 1L); List<Comment> comments = article.getComments(); // 해당 게시글의 모든 댓글 가져오기 🚀5️⃣ 단방향 @OneToMany vs 양방향 @OneToMany 1️⃣ 단방향 @OneToMany @Entity public class Article { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private String content; @OneToMany @JoinColumn(name = "article_id") // FK를 직접 관리 (주인 역할) private List<Comment> comments = new ArrayList<>(); // Getter, Setter } ✅ 장점. 단순한 구조. 불필요한 mappedBy 없이 @JoinColumn을 통해 FK 직접 관리 가능 ❌ 단점. 데이터 삽입 시 추가적인 SQL 실행 발생 @OneToMany 단방향 관계에서 @JoinColumn을 사용하면 INSERT 쿼리가 두 번 실행됨 (➞ 댓글 삽입 후, 게시글 ID 업데이트) 2️⃣ 양방향 @OneToMany + @ManyToOne @Entity public class Article { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private String content; @OneToMany(mappedBy = "article") // Comment의 article 필드를 주인으로 설정 private List<Comment> comments = new ArrayList<>(); // Getter, Setter } ✅ 장점. 성능 최적화 가능 (FK는 Comment.article이 관리). INSERT 쿼리 실행이 한 번만 발생. 객체 그래프 탐색이 편리함 (article.getComments() 가능). ❌ 단점 mappedBy로 인해 데이터 저장이 Comment 쪽에서 이루어져야 함. ✅6️⃣ 정리 ✔️ @OneToMany는 하나(One)의 엔티티가 여러 개(Many)의 엔티티를 참조할 때 사용. ✔️ 양방향 관계에서는 @OneToMany(mappedBy = “필드명”) + @ManyToOne 조합 사용 ✔️ 단방향 @OneToMany보다는 양방향을 사용하는 것이 일반적 ✔️ 외래 키(FK)는 @ManyToOne 쪽에서 관리하며, @OneToMany는 읽기 전용 📌 게시글-댓글 관계처럼 1:N 관계가 필요할 때 @OneToMany를 사용하면 됩니다.
Backend Development
· 2025-02-18
📚[Backend Development] @ManyToOne이란 무엇일까요?
“📚[Backend Development] @ManyToOne이란 무엇일까요?” 🍎 Intro. @ManyToOne은 다대일(N:1) 관계를 매핑할 때 사용합니다. 즉, 여러 개(Many)의 엔티티가 하나(One)의 엔티티를 참조하는 구조입니다. ✅1️⃣ @ManyToOne 예제. 게시글(Article)과 댓글(Comment) 관계를 예로 들어보겠습니다. 하나의 게시글(Article)에 여러 개의 댓글(Comment)이 달릴 수 있습니다. 1️⃣ Article 엔티티 (게시글) @Entity public class Article { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private String content; // Getter, Setter } 2️⃣ Comment 엔티티 (댓글) @Entity public class Comment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String content; @ManyToOne @JoinColumn(name = "article_id") // 외래 키 컬럼명 설정 private Article article; // Getter, Setter } ✅2️⃣ @ManyToOne 설명. @ManyToOne을 사용하여 여러 개의 댓글(Comment)이 하나의 게시글(Article)을 참조하도록 설정합니다. @JoinColumn(name = “article_id”)를 통해 comment 테이블에 article_id 외래 키(FK)를 생성합니다. ✅3️⃣ 데이터베이스 테이블 구조. 위 코드를 실행하면 데이터베이스는 다음과 같은 테이블이 생성됩니다. 📌 article 테이블 id title content 1 “Hello JPA” “JPA 배우기” 2 “Spring Boot” “Spring 공부” 📌 comment 테이블 (article_id FK 포함) id content article_id(FK) 1 “좋은 글이네요!” 1 2 “유익한 정보 감사합니다.” 1 3 “Spring 최고!” 2 📌 article_id 컬럼이 게시글(Article)을 참조하는 외래 키(FK)입니다. 즉, comment 테이블의 여러 행이 article_id를 통해 같은 article을 가리킬 수 있습니다. ✅4️⃣ 데이터 조회 ✅ 특정 게시글에 속한 댓글 가져오기. 게시글 ID(articleId)가 1번인 댓글을 가져오려면: List<Comment> comments = entityManager.createQuery( "SELECT c FROM c WHERE c.article.id = :articleId", Comment.class) .setParameter("articleId", 1L) .getResultList();
Backend Development
· 2025-02-18
📚[Backend Development] 단방향과 @OneToOne이란 무엇일까요?
“📚[Backend Development] 단방향과 @OneToOne이란 무엇일까요?” 🍎 Intro. 단방향 @OneToOne 관계는 엔티티 간의 1:1 관계를 매핑할 때, 한쪽 엔티티에서만 관계를 관리하는 방식입니다. 즉, 한 엔티티에서만 다른 엔티티를 참조하고, 반대쪽에서는 이를 알지 못하는 상태입니다. ✅1️⃣ 예제 코드 예를 들어, User 엔티티와 UserProfile 엔티티가 1:1 관계를 가진다고 가정해봅시다. 📝 User 엔티티에서 UserProfile 엔티티를 단방향으로 참조하는 경우: @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; @OneToOne @JoinColumn(name = "profile_id") // User 테이블의 profile_id 컬럼이 UserProfile의 id를 참조 private UserProfile profile; // Getter, Setter } 📝 UserProfile 엔티티: @Entity public class UserProfile { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String bio; private String website; // Getter, Setter } ✅ 설명: @OneToOne을 사용하여 User 엔티티가 UserProfile 엔티티를 참조합니다. @JoinColumn(name = “profile_id”)를 사용하여 User 테이블에 profile_id 컬럼이 생성됩니다. User 테이블에 profile_id라는 외래 키(FK) 컬럼을 추가하고, 이 컬럼이 UserProfile 테이블의 id(PK)를 참조하도록 만듭니다. 즉, User 테이블의 profile_id가 UserProfile 테이블의 id를 참조하는 FK이다. 하지만 UserProfile 엔티티에는 User와의 관계를 알 수 있는 정보가 없습니다. ➞ 이것이 단방향 관계입니다. ✅ 살제 데이터베이스 테이블 예시: 이 코드를 기반으로 JPA가 생성하는 테이블을 보면 다음과 같이 됩니다. 📊 user 테이블 id username profile_id(FK) 1 Alice 101 2 Bob 102 📊 user_profile 테이블 id bio website 101 “Gamer” “alice.com” 102 “Developer” “bob.dev” 📌 즉, user.profile_id는 user_profile.id를 참조(FK)하는 구조입니다. 따라서 UserProfile 엔티티에는 profile_id가 따로 필요하지 않습니다. 대신 기본 키(id)가 User 엔티티의 외래 키(profile_id)로 사용됩니다. ✅2️⃣ 단방향 관계의 특징. ✅ 장점. 구조가 단순하고 이해하기 쉽다. 한쪽에서만 참조하므로 불필요한 연관관계 로딩을 방지할 수 있다. ❌ 단점. 반대쪽(UserProfile)에서 User를 조회할 방법이 없다. UserProfile이 자신을 참조하는 User가 누구인지 알고 싶다면 별도의 쿼리를 작성해야 한다. ✅3️⃣ 단방향 관계 조회. 사용자가 프로필 정보를 가져오는 코드를 작성하면 다음과 같습니다. User user = entityManager.find(User.class, 1L); UserProfile profile = user.getProfile(); // User -> UserProfile 조회 가능.
Backend Development
· 2025-02-17
📚[Backend Development] 빌더 패턴 사용시 @AllArgsConstructor(access = AccessLevel.PRIVATE)을 활용하는 이유
“📚[Backend Development] 빌더 패턴 사용시 @AllArgsConstructor(access = AccessLevel.PRIVATE)을 활용하는 이유” 🍎 Intro. 빌더 패턴을 사용할 때, @AllArgsConstructor(access = AccessLevel.PRIVATE)를 추가하는 이유를 설명하겠습니다. ✅1️⃣ @AllArgsConstructor가 하는 역할. @AllArgsConstructor는 모든 필드를 포함하는 생성자를 자동으로 생성합니다. 하지만 빌더 패턴을 사용할 경우, 생성자를 직접 호출하지 않고 빌더를 통해 객체를 생성하는 것이 목적입니다. 따라서, 생성자의 접근 제한을 private으로 설정하면, 빌더를 통한 생성만 허용할 수 있습니다. ✅2️⃣ 빌더 패턴 적용 시, @AllArgsConstructor(access = AccessLevel.PRIVATE)가 필요한 이유 ❌ 잘못된 예제 (빌더 패턴 사용했지만, 생성자도 public) @AllArgsConstructor // (기본값이 PUBLIC) @Builder public class Comment { private Long commentId; private String content; private Long articleId; private Long parentCommentId; private Long writerId; private Boolean deleted; private LocalDateTime createdAt; } ✅ 문제점: @AllArgsConstructor의 기본 접근 제어자가 public이므로, 빌더를 사용하지 않고 생성자를 직접 호출하여 객체를 만들 수 있음. 빌더를 사용하는 목적이 객체 생성 시 가독성을 높이고 선택적으로 필드를 초기화 할 수 있도록 하기 위함인데, 생성자가 public이면 빌더 사용을 강제할 수 없음. 🛠️ 해결 방법(@AllArgsConstructor(access = AccessLevel.PRIVATE)) @AllArgsConstructor(access = AccessLevel.PRIVATE) // 생성자를 PRIVATE으로 설정 @Builder public class Comment { private Long commentId; private String content; private Long articleId; private Long parentCommentId; private Long writerId; private Boolean deleted; private LocalDateTime createdAt; } ✅ 이렇게 하면: 객체를 직접 생성하는 것을 막고, 빌더를 통한 생성만 가능하도록 제한 가능. 불필요한 생성자 호출을 막고, 가독정이 좋은 빌더 패턴을 강제할 수 있음. 객체의 필드가 많아질수록, 빌더 패턴이 더 유용하게 동작하게 됨. ✅3️⃣ 정리 - 필요한 이유. 문제점 해결 방법 @AllArgConstructor 기본값이 public이므로, 직접 생성자 호출이 가능함. @AllArgsConstructor(access = AccessLevel.PRIVATE)를 사용하여 생성자 접근 제한. 빌더를 사용해도 생성자를 직접 호출할 수 있어 일관성이 떨어짐. 빌더를 강제하여 가독성 및 유지보수성을 높임. 객체 필드가 많아질 경우, 생성자 호출보다 빌더 패턴이 더 유리함. 빌더를 강제하여 더 가독성이 좋은 코드 유지 가능. ✅ 즉, @AllArgsCOnstructor(access = AccessLevel.PRIVATE)를 사용하면, 빌더를 통한 객체 생성을 강제하여 코드 일관성을 유지할 수 있습니다.
Backend Development
· 2025-02-15
📚[Backend Development] 계층형 댓글 목록 조회 시 페이징 처리 방법
“📚[Backend Development] 계층형 댓글 목록 조회 시 페이징 처리 방법” 💡 가정: 계층별 오래된 순으로 페이징됨. 1페이지 당 2개의 댓글을 보여줌. 👉 이 가정이 맞는지 검토하고, 어떻게 페이징이 이루어지는지 확인해봅시다. ✅1️⃣ 기본적인 계층형 정렬 방식. 📌 계층형 댓글 조회 시 일반적인 정렬 규칙. 1. 최상위 댓글(부모 댓글)이 먼저 정렬됨 (오래된 순). 2. 각 부모 댓글의 하위 댓글(자식 댓글)이 정렬됨 (오래된 순). 3. 같은 계층 내에서도 오래된 순으로 정렬됨. 📌 계층형 정렬된 목록. ✅2️⃣ 1페이지 당 2개의 댓글을 보여줄 경우. 계층형 구조에서는 단순 LIMIT & OFFSET을 사용하면 데이터가 끊어질 수 있음. 트리 구조를 유지하면서 정렬된 순서로 페이징해야 함. 📌 전체 페이징 처리 모습. 📌 1페이지 (첫 2개 댓글) 최상위 댓글 1개(댓글 1) + 그에 대한 하위 댓글(댓글 2)을 포함 하위 댓글이 있는 경우, 다음 댓글을 포함할지 여부는 페이징 로직에 따라 결정됨. 📌 2페이지 (다음 2개 댓글) 첫 번째 페이지에서 댓글 1 ➞ 댓글 2까지 가져왔으므로, 이제 댓글 2의 하위 댓글부터 표시. 즉, 댓글 3과 댓글 5가 다음 페이지에 노출됨. 📌 3페이지 (다음 2개 댓글) 댓글 1 트리가 끝났으므로, 이제 댓글 4를 표시. 댓글 4의 하위 댓글인 댓글 6도 같이 표시됨. ✅3️⃣ 정리 페이지 번호 출력되는 댓글 목록 1 페이지 댓글 1, 댓글 2 2 페이지 댓글 3, 댓글 5 3 페이지 댓글 4, 댓글 6 ✔️ 즉, 최상위 댓글을 기준으로 정렬하되, 계층 구조를 유지하면서 페이징이 이루어짐. ✔️ 각 댓글의 하위 댓글을 보여줄 때, 부모 댓글이 포함된 상태에서 하위 댓글이 순차적으로 정렬됨. ✅4️⃣ 추가적인 고려 사항. ✅ 페이징 성능을 고려한 SQL 쿼리. 계층형 댓글 페이징을 위한 CTE(Common Table Expression) 또는 Recursive Query 활용 가능. ORDER BY parent_id, created_at ASC와 같은 정렬 방식 적용. ✅ UI에서의 표시 방식 첫 번째 페이지에서 댓글 1 ➞ 댓글 2까지만 표시할 수 있고, 첫 번째 페이지에서 댓글 1 ➞ 댓글 2 ➞ 댓글 3 ➞ 댓글 5까지 한 번에 표시할 수도 있음 “더 보기” 버튼을 활용하여 동적 로딩 방식도 고려 가능.
Backend Development
· 2025-02-14
📚[Backend Development] 계층형 대댓글에서 댓글을 삭제하면 어떤 현상이 발생할까요? 3️⃣
“📚[Backend Development] 계층형 대댓글에서 댓글을 삭제하면 어떤 현상이 발생할까요? 3️⃣” ✅ “삭제 표시(Soft Delete)” 방식에서 댓글 5를 삭제하면 어떻게 될까? 💡 가정. Soft Delete(삭제 표시) 방식을 사용 중. 댓글 5는 하위 댓글이 없으므로 완전히 삭제될 것이다. 👉 이 가정이 맞는지 확인해 봅시다. ✅1️⃣ 댓글 5 삭제 전의 구조. 댓글 1과 댓글 2는 삭제 표시(Soft Delete) 상태. 댓글 3과 댓글 5는 남아 있음. 이 상태에서 댓글 5를 삭제하면 어떻게 될까? 🤔 ✅2️⃣ Soft Delete vs Physical Delete 차이점 삭제 방식 설명 댓글 5 삭제 시 결과 Soft Delete (논리 삭제) 데이터베이스에서 삭제하지 않고 is_deleted = TRUE로 표시만 함 댓글 5가 “삭제된 댓글입니다.”로 남음 Physical Delete (물리 삭제) 데이터베이스에서 실제 삭제 댓글 5가 완전히 제거됨 ✔️ Soft Delete라면 “삭제된 댓글입니다.”로 남지만, Physical Delete라면 댓글 5가 실제로 삭제됨. ✔️ 댓글 5는 하위 댓글이 없기 때문에 완전 삭제(Physical Delete)되는 것이 일반적. ✅3️⃣ 댓글 5 삭제 후의 새로운 구조 ✅ Soft Delete 적용 시 (논리 삭제) 댓글 5가 삭제 표시로 남음(Soft Delete) → “삭제된 댓글입니다.”로 보임. 댓글 3이 남아 있으므로 댓글 2의 구조는 유지됨. ✅ Physical Delete 적용 시 (완전 삭제) 댓글 5가 완전히 삭제된(Physical Delete) 댓글 2하위에서 댓글 3만 남음. ✅4️⃣ 결론: 댓글 5는 완전 삭제가 이루어질 가능성이 높음 Soft Delete를 적용하더라도, 댓글 5는 하위 댓글이 없기 때문에 완전히 삭제(Physical Delete) 되는 것이 일반적. 댓글 5를 Soft Delete 처리할 필요가 없으며, 실제로 DB에서 제거되는 것이 최적의 방식.
Backend Development
· 2025-02-13
📚[Backend Development] 계층형 대댓글에서 댓글을 삭제하면 어떤 현상이 발생할까요? 1️⃣
“📚[Backend Development] 계층형 대댓글에서 댓글을 삭제하면 어떤 현상이 발생할까요? 1️⃣” 🍎 Intro. 댓글을 삭제하는 방식에는 여러 가지가 있습니다. 데이터베이스의 외래 키(Foreign Key) 설정 및 삭제 정책에 따라 다른 현상이 발생할 수 있습니다. ✅1️⃣ 댓글 2의 구조 분석. 댓글 2는 댓글 1의 자식 댓글(대댓글). 댓글 3, 댓글 5는 댓글 2의 자식 대댓글. 즉, 댓글 2를 삭제하면 댓글 3과 댓글 5가 고아 상태(부모 없는 상태)가 됨. ✅2️⃣ 댓글 2 삭제 시 발생할 수 있는 시나리오 📌 시나리오 1️⃣: 연쇄 삭제 (Cascading Delete) 댓글 2를 삭제하면 댓글 3과 댓글 5도 함께 삭제됨. ON DELETE CASCADE 옵션이 설정되어 있는 경우 발생. 👉 결과. 삭제 후 남아있는 댓글 목록: 댓글 1 댓글 4 댓글 6 댓글 3과 댓글 5까지 함께 삭제되므로, 해당 스레드 전체가 사라짐. 📌 시나리오 2️⃣: 고아 댓글 처리 (Orphan Handling) 댓글 2를 삭제하면 댓글 3과 댓글 5의 부모(parent_id)를 NULL로 설정. 즉, 댓글 3과 댓글 5가 독립적인 최상위 댓글이 됨. 댓글 4와 댓글 6의 계층 구조는 유지됨 ➞ 댓글 6의 parent_id = 4 👉 결과. 삭제 후 남아있는 댓글 목록: 댓글 1 댓글 3 (기존 댓글 2의 자식 → 최상위 댓글로 이동) 댓글 5 (기존 댓글 2의 자식 → 최상위 댓글로 이동) 댓글 4 └ 댓글 6 📌 시나리오 3️⃣: 부모 댓글 대체 (Reparenting) 댓글 2를 삭제하면 댓글 3과 댓글 4의 부모를 댓글 1로 변경. 즉, 댓글 2의 자식들이 댓글 1의 직접적인 자식 댓글이 됨 👉 결과. 삭제 후 남아있는 댓글 목록: 댓글 1 └ 댓글 3 └ 댓글 5 댓글 4 └ 댓글 6 ✅3️⃣ 댓글 2 삭제를 처리하는 방법 선택. 삭제 방식 설명 장점 단점 연쇄 삭제 (Cascade) 댓글 2를 삭제하면 댓글 3, 5도 삭제 데이터 정합성 유지 유저가 예상치 못한 삭제 발생 가능 고아 댓글 처리 (Orphan) 댓글 3과 5가 최상위 댓글이 됨 삭제 후에도 데이터 보존 UI에서 댓글 관계가 깨질 수 있음 부모 댓글 대체(Reparenting) 댓글 3과 댓글 5가 댓글 1의 자식이 됨 대댓글 구조 유지 데이터 수정이 필요 ✅4️⃣ MySQL에서 삭제 처리 방식 예제. 1️⃣ ON DELETE CASCADE (연쇄 삭제) ALTER TABLE comments ADD CONSTRAINT fk_parent FOREIGN KEY (parent_id) REFERENCES comments (comment_id) ON DELETE CASCADE; 댓글 2를 삭제하면 댓글 3과 댓글 5도 자동으로 삭제됨. 2️⃣ 부모 댓글을 NULL로 설정 (고아 댓글 처리) UPDATE comments SET parent_id = NULL WHERE parent_id = 2; DELETE FROM comments WHERE comment_id = 2; 댓글 3과 댓글 5가 부모 없이 최상위 댓글로 변경됨. 3️⃣ 부모 댓글 변경 (Reparenting) UPDATE comments SET parent_id = 1 WHERE parent_id = 2; DELETE FROM comments WHERE comment_id = 2; 댓글 3과 댓글 5가 댓글 1의 자식이 됨. ✅5️⃣ 결론 댓글 2를 삭제하면 댓글 3과 댓글 5의 처리 방법에 따라 다른 결과가 발생. 어떤 방식이 가장 적절한지는 비즈니스 로직과 UX에 따라 결정해야 함. 보통은 부모 댓글을 삭제해도 대댓글이 남도록 처리(고아 댓글 처리 or 부모 댓글 변경)하는 경우가 많음.
Backend Development
· 2025-02-12
📚[Backend Development] 계층형 대댓글에서 댓글을 삭제하면 어떤 현상이 발생할까요? 2️⃣
“📚[Backend Development] 계층형 대댓글에서 댓글을 삭제하면 어떤 현상이 발생할까요? 2️⃣” ✅ 댓글 1을 삭제할 때, 삭제 표시만 될 것인가? 📌 가정 댓글 2는 이미 삭제 상태(“삭제된 댓글입니다.”)로 표시됨. 그렇다면, 댓글 1을 삭제하면 댓글 1도 삭제 표시만 될 것이다. 즉, 댓글 1이 완전히 삭제되지 않고, “삭제된 댓글입니다.”로 유지될 것입니다. 👉 이 가정이 맞는지 한 번 확인해봅시다. ✅1️⃣ 댓글 1 삭제 전의 구조 (댓글 2는 삭제 상태) 댓글 2가 삭제 상태지만, 댓글 3과 댓글 5는 유지됨. 이 상태에서 댓글 1을 삭제하면 어떻게 될까? ✅2️⃣ 댓글 1 삭제 처리 방식에 따른 결과. 댓글이 삭제될 때, 적용할 수 있는 방법은 두 가지 입니다. 📌1️⃣ 연쇄 삭제 (Cascade Delete) 📝 설명. 댓글 1이 삭제되면 그 하위 댓글도 모두 삭제됨. 즉, 댓글 1이 삭제되면 댓글 2, 댓글 3, 댓글 5도 함께 삭제됨. 👉 결과 구조 (Cascade Delete 적용 시) ❌ 이 방식은 가정과 다르게 댓글 1이 삭제되면서 하위 댓글도 모두 삭제됨. 📌2️⃣ 삭제 표시 (Soft Delete) 📝 설명. 댓글 1을 삭제하면 댓글 2처럼 “삭제된 댓글입니다.”로 표시됨. 즉, 댓글 1이 삭제되더라도 댓글 3과 댓글 5가 남아 있기 때문에 완전히 사라지지 않음. 가정과 동일한 방식 👉 결과 구조 (Soft Delete 적용 시) ✅ 이 방식이 사용자의 가정과 일치합니다. ✅ 댓글 1은 “삭제된 댓글입니다.” 상태가 되고, 댓글 3과 댓글 5는 그대로 남음. ✅3️⃣ 가정이 맞는가? ✔ 결론: 사용자의 가정은 “Soft Delete” 방식이 적용될 경우 맞습니다. ✔ 즉, 댓글 1도 삭제 상태로 표시되지만, 하위 댓글이 남아 있기 때문에 완전히 사라지지 않습니다. ✔ 만약 “Cascade Delete”가 적용되었다면, 댓글 1이 삭제되면서 하위 댓글(2, 3, 5)도 모두 삭제되므로 사용자의 가정과 다릅니다. ✅4️⃣ 실제 서비스에서는 어떤 방식을 사용할까? 삭제 방식 설명 적용 서비스 Cascade (완전 삭제) 부모 댓글이 삭제되면 하위 댓글도 삭제됨 일부 게시판, 블로그 Soft Delete(삭제 표시 유지) 부모 댓글이 삭제되더라도 하위 댓글이 있으면 “삭제된 댓글입니다.”로 유지됨 네이버 카페, 인스타그램, 페이스북, 유튜브 댓글 📌 일반적으로 커뮤니티나 SNS 서비스에서는 Soft Delete를 사용하여 부모 댓글을 “삭제된 댓글입니다.”로 유지하는 경우가 많습니다. 📌 즉, 가정이 실제 서비스에서 많이 사용되는 방식과 일치합니다.
Backend Development
· 2025-02-12
📚[Backend Development] 최대 2 Depth의 계층형 대댓글이란?
“📚[Backend Development] 최대 2 Depth의 계층형 대댓글이란?” 🍎 Intro. 댓글(Parent) ➞ 대댓굴(Child)까지만 허용하며, 대댓글의 대댓글(Child of Child)은 허용하지 않는 구조입니다. 즉, 댓글의 깊이가 최대 2단계까지만 유지 되며, 1 Depth (최상위 댓글) 2 Depth (대댓글) 이후에는 더 이상 하위 대댓글을 추가할 수 없는 방식입니다. ✅1️⃣ 최대 2 Depth 계층형 대댓글이 필요한 이유 ❌1️⃣ 일반적인 계층형 댓글 방식의 문제점. 댓글이 무한히 중첩될 경우, 데이터 조회 및 정렬이 복잡해지고 성능 저하 가능성. UI에서 너무 깊은 계층 구조는 사용자 경험(UX)에 좋지 않음. 무한 재귀 호출 방지를 위해 계층을 제한하는 것이 일반적. ✅2️⃣ 최대 2 Depth 계층형 대댓글의 장점. UI/UX 개선 ➞ 대댓글이 많아도 가독성이 유지됨. SQL 성능 최적화 가능 ➞ 복잡한 재귀 쿼리 없이 간단한 JOIN으로 해결 가능. 프론트엔드에서 구현이 쉬움 ➞ 2 Depth까지만 유지하므로 댓글 정렬이 단순함. ✅2️⃣ 최대 2 Depth의 계층형 대댓글 테이블 구조. CREATE TABLE comments ( comment_id BIGINT AUTO_INCREMENT PRIMARY KEY, article_id BIGINT NOT NULL, -- 게시글 ID parent_id BIGINT NULL, -- 부모 댓글 ID (NULL이면 최상위 댓글) depth INT NOT NULL DEFAULT 1, -- 댓글의 깊이 (1: 일반 댓글, 2: 대댓글) author VARCHAR(255) NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (parent_id) REFERENCES comments(comment_id) ON DELETE CASCADE ); ✅ 테이블 주요 컬럼 comment_id ➞ 댓글의 고유 ID. article_id ➞ 댓글이 속한 게시글 ID. parent_id ➞ 부모 댓글 ID (NULL이면 최상위 댓글). depth ➞ 댓글의 깊이 (1이면 일반 댓글, 2이면 대댓글). author ➞ 작성자. content ➞ 댓글 내용. created_at ➞ 댓글 작성 시간. ✅3️⃣ 최대 2 Depth 계층형 대댓글의 규칙. Depth 설명 1 Depth 일반 댓글 (Parent) 2 Depth 대댓글 (Child) 3 Depth 이상 ❌ 허용하지 않음 ✅4️⃣ 최대 2 Depth의 계층형 대댓글 목록 조회 API 구현. 🛠️1️⃣ Entity (JPA 기반 계층형 댓글) import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Getter @NoArgsConstructor @Table(name = "comments") public class Comment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long commentId; @ManyToOne @JoinColumn(name = "article_id", nullable = false) private Article article; @ManyToOne @JoinColumn(name = "parent_id") private Comment parent; // 부모 댓글 @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) private List<Comment> replies = new ArrayList<>(); // 대댓글 리스트 private Integer depth; // 댓글 깊이 (1: 일반 댓글, 2: 대댓글) private String author; private String content; private LocalDateTime createdAt = LocalDateTime.now(); } ✅ 댓글의 깊이를 depth 필드로 저장하여 최대 2 Depth까지만 허용. 🛠️2️⃣ Repository (2 Depth까지 댓글 조회) import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface CommentRepository extends JpaRepository<Comment, Long> { List<Comment> findByArticle_ArticleIdDepthOrderByCreatedAtAsc(Long articleId, Integer depth); } ✅ 최대 depth = 2까지만 조회하도록 설정. 🛠️3️⃣ Service (비즈니스 로직) import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.List; @Service @RequiredArgsConstructor public class CommentService { private final CommentRepository commentRepository; public List<CommentResponse> getCommentsByArticle(Long articleId) { return commentRepository.findByArticle_ArticleIdAndDepthOrderByCreatedAtAsc(articleId, 1) .stream() .map(CommentResponse::fromEntity) .toList(); } public List<CommentResponse> getRepliesByComment(Long parentId) { return commentRepository.findByArticle_ArticleIdAndDepthOrderByCreatedAtAsc(parentId, 2) .stream() .map(CommentResponse::fromEntity) .toList(); } } ✅ 최대 2 Depth까지만 조회하는 API 로직 구현. 🛠️4️⃣ Controller (API 요청 처리) import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/articles/{articleId}/comments") @RequiredArgsConstructor public class CommentController { private final CommentService commentService; @GetMapping public ResponseEntity<List<CommentResponse>> getComments(@PathVariable Long articleId) { return ResponseEntity.ok(commentService.getCommentsByArticle(articleId)); } @GetMapping("/{commentId}/replies") public RespinseEntity<List<CommentResponse>> getReplies(@PathVariable Long commentId) { return ResponseEntity.ok(commentService.getRepliesByComment(commentId)); } } ✅ RESTful API로 최대 2 Depth까지 댓글 및 대댓글 조회 가능. ✅5️⃣ API 응답 예시. [ { "commentId": 1, "author": "John Doe", "content": "좋은 글이네요!", "createdAt": "2025-02-01T12:00:00Z", "replies": [ { "commentId": 2, "author": "Alice", "content": "저도 그렇게 생각해요!", "createdAt": "2025-02-01T12:05:00Z" } ] }, { "commentId": 3, "author": "Bob", "content": "궁금한 점이 있어요!", "createdAt": "2025-02-01T12:10:00Z", "replies": [] } ] ✅ 최대 2 Depth까지만 유지되며, replies 필드를 이용하여 대댓글을 표현. ✅6️⃣ SQL 쿼리 방식. -- 최상위 댓글(1 Depth) 조회 SELECT * FROM comments WHERE article_id = 123 AND depth = 1 ORDER BY created_at ASC; -- 특정 댓글(2 Depth)의 대댓글 조회 SELECT * FROM comments WHERE parent_id = 1 AND depth = 2 ORDER BY created_at ASC; ✅ SQL을 활용하여 2 Depth까지만 조회 가능하도록 제한. ✅7️⃣ 최대 2 Depth 계층형 대댓글의 활용 사례 서비스 설명 블로그 블로그 게시글의 댓글 + 대댓글(ex: 네이버 블로그) 커뮤니티 게시판 댓글 + 대댓글 (ex: Reddit) SNS SNS 댓글 + 대댓글 (ex: 인스타그램, 트위터) 이커머스 상품 리뷰의 대댓글 (ex: 아마존) ✅8️⃣ 결론. 최대 2 Depth의 계층형 대댓글은 댓글 ➞ 대댓글까지만 허용하고, 더 이상 하위 댓글을 허용하지 않는 방식. 데이터 정렬이 단순해지고 성능 최적화 가능. Spring Boot + JPA 기반으로 쉽게 구현 가능. 대규모 트래픽에서도 적절한 성능을 유지하면서 안정적인 댓글 시스템 제공 가능.
Backend Development
· 2025-02-11
📚[Backend Development] 단방향과 양방향의 개념에 대하여.
“📚[Backend Development] 단방향과 양방향의 개념에 대하여.” 🍎 Intro. JPA에서 엔티티 간의 관계를 설정할 때 단방향과 양방향 관계를 정의할 수 있습니다. 이는 데이터베이스의 외래 키(Foreign Key) 관계를 객체 지향적으로 매핑하는 방식에 따라 달라집니다. ✅1️⃣ 단방향 관계 (Unidirectional) 단방향 관계는 한쪽 엔티티만 다른 엔티티를 참조하는 방식입니다. 📝 예제: 단방향 @OneToOne 관계. @Entity public class Passport { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String passportNumber; } @Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne @JoinColumn(name = "passport_id") // 외래 키를 Person 테이블이 가짐 private Passport passport; } 📌 특징 Person 엔티티에서만 Passport를 참조할 수 있습니다. Passport 엔티티에서는 Person을 전혀 모릅니다. 테이블 구조에서는 Person 테이블에 passport_id라는 외개 키가 존재합니다. 데이터 조회 시 Person을 가져올 때 Passport도 함께 조회할 수 있습니다. ✅2️⃣ 양방향 관계 (Bidirectional) 양방향 관계는 두 엔티티가 서로를 참조하는 방식입니다. 📝 예제: 양방향 @OneToOne 관계. @Entity public class Passport { @Id @GeneratedValue(startegy = GenerationType.IDENTITY) private Long id; private String passportNumber; @OneToOne(mappedBy = "passport") // Person 엔티티의 passport 필드와 연결 private Person person; } @Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToOne @JoinColumn(name = "passport_id") // 실제 외래 키를 소유 private Passport passport; } 📌 특징 Person이 Passport를 참조하고, Passport도 Person을 참조합니다. Person이 외래 키를 소유(@JoinColumn(name = “passport_id))” Passport에서 mappedBy = “passport”를 사용하여 반대편에서 매핑을 담당함. 두 엔티티가 서로 참조하기 때문에 양방향 탐색 가능(예: passport.getPerson()) ✅3️⃣ 단방향 VS 양방향 비교. 구분 단방향 관계 양방향 관계 참조 방향 한쪽 엔티티만 다른 엔티티를 참조 두 엔티티가 서로 참조 테이블 구조 한쪽 테이블에만 외래 키 존재 테이블 구조는 동일하나 객체에서 상호 참조 조회 방향 한쪽에서만 조회 가능 양쪽에서 조회 가능 사용 예 단순한 연관 관계 상관 관계가 필요한 경우 ✅4️⃣ 언제 단방향/양방향을 선택해야 할까? 1️⃣ 단방향이 더 적합한 경우. 반대 방향에서 참조할 필요가 없는 경우 예: Order ➞ Payment (주문은 결제를 참조하지만, 결제는 주문을 참조할 필요 없음) 성능을 최적화하고 불필요한 데이터 로딩을 방지하고 싶은 경우 양방향 관계를 만들면 불필요한 연관 객체까지 로딩될 수 있음. FetchType.LAZY를 설정하더라도 관리 부담이 커질 수 있음. 2️⃣ 양방향이 더 적합한 경우. 양쪽에서 참조할 필요가 있는 경우 예: Member ↔ Team (회원이 팀을 참조하고, 팀도 회원 목록을 관리해야 함) 반대 엔티티를 쉽게 조회해야 하는 경우 예를 들어 Passport에서 Person을 조회하는 기능이 자주 필요하다면 양방향이 유리함. OneToMany, ManyToOne 관계에서는 성능 고려 후 양방향 설정을 할 수도 있음. ✅5️⃣ 정리 @OneToOne, @OneToMany, @ManyToOne, @ManyToMany 관계는 단방향과 양방향이 모두 가능합니다. 단방향은 한쪽에서만 참조, 양방향은 서로 참조합니다. 양방향 관계에서는 mappedBy를 사용하여 연관 관계의 주인을 지정해야 합니다. 불필요한 양방향 관계를 피하고, 필요한 경우에만 적용하여 성능과 유지보수성을 고려해야 합니다. 👉 일반적으로 단반향을 기본으로 하고, 필요할 때만 양방향을 추가하는 것이 좋습니다.
Backend Development
· 2025-02-11
📚[Backend Development] 계층형 댓글 목록 조회란 무엇일까요?
“📚[Backend Development] 계층형 댓글 목록 조회란 무엇일까요?” 🍎 Intro. 계층형 댓글(Hierachical Commmeents)이란, 댓글과 대댓글(답글)을 계층적으로 표시하는 방식입니다. 이는 일반적인 1차원 목록 형태의 댓글 조회와 달리, 부모-자식 관계를 유지하는 댓글 시스템입니다. ✅1️⃣ 계층형 댓글 목록 조회가 필요한 이유. ❌1️⃣ 일반적인 평면 댓글(flat comments) 방식의 한계. 일반적으로 게시글에 대한 댓글을 단순 목록(List) 형태로 조회. 하지만 답글(대댓글)이 많아질 경우, 계층 구조가 필요. ✅2️⃣ 계층형 댓글의 장점. 댓글에 대한 대댓글(답글)을 구조적으로 표현 가능. 유저가 대화 흐름을 쉽게 파악할 수 있음. 재귀적인 구조를 활용하여 대댓글이 몇 단계까지 달려도 정렬 가능. ✅2️⃣ 계층형 댓글의 데이터 구조. 계층형 댓글을 저장하는 방법은 여러 가지가 있지만, 일반적으로 “부모 댓글(parentId)”를 저장하여 댓글 간 관계를 유지합니다. 📌1️⃣ 테이블 구조 CREATE TABLE comments ( comment_id BIGINT AUTO_INCREMENT PRIMARY KEY, article_id BIGINT NOT NULl, -- 게시글 ID parent_id BIGINT NULL, -- 부모 댓글 ID (NULL이면 최상위 댓글) author VARCHAR(255) NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (parentt_id) REFERENCES comments(comment_id) ON DELETE CASCADE ); ✅ 주요 컬럼. comment_id ➞ 댓글의 고유 ID. article_id ➞ 해당 댓글이 속한 게시글 ID. parent_id ➞ 부모 댓글 ID (최상위 댓글이면 NULL). author ➞ 댓글 작성자. content ➞ 댓글 내용. created_at ➞ 댓글 작성 시간. ✅3️⃣ 계층형 댓글 목록 조회 API 구현 (Spring Boot + JPA) 🛠️1️⃣ Entity (계층형 구조) import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Getter @NoArgsConstructor @Entity @Table(name = "comments") public class Comment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long commentId; @ManyToOne @JoinColumn(name = "article_id", nullable = false) private Article article; @ManyToOne @JoinColumn(name = "parent_id") private Comment parent; // 부모 댓글 @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) private List<Comment> replies = new ArrayList<>(); // 대댓글 리스트 private String author; private String content; private LocalDateTime createdAt = LocalDateTime.now(); } ✅ 부모 댓글(parent)과 대댓글(replies) 관계를 유지하여 계층 구조 형성. 🛠️2️⃣ Repository (계층형 댓글 조회) import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface CommentRepository extends JpaRepository<Comment, Long> { List<Comment> findByArticle_ArticleIdAndParentIsNullOrderByCreatedAtDesc(Long articleId); } ✅ 최상위 댓글(부모가 없는 댓글)만 가져옴 ➞ 이후 replies 필드에서 대댓글을 가져옴. 🛠️3️⃣ Service (계층형 댓글 로직 구현) import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.List; @Service @RequireArgsConstructor public class CommentService { private final CommentService { private final CommentRepository commentRepository; public List<CommentResponse> getCommentsByArticle(Long articleId) { List<Comment> comments = commentRepository.findByArticle_ArticleIdAndParentIsNullOrderByCreatedAtDesc(articleId); return comments.stream().map(CommentResponse::fromEntity).toList(); } } } ✅ 최상위 댓글만 조회하고, 대댓글은 replies 필드를 통해 재귀적으로 가져옴. 🛠️4️⃣ Controller (API 요청 처리) import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/articles/{articleId}/comments") @RequiredArgsConstructor public class CommentController { private final CommentService commentService; @GetMapping public ResponseEntity<List<CommentResponse>> getComments(@PathVariable Long articleId) { return ResponseEntity.ok(commentService.getCommentsByArticle(articleId)); } } ✅ RESTful API 형식으로 GET /api/articles/{articleId}comments 요청을 처리. ✅4️⃣ 계층형 댓글 조회 API의 응답 예시 [ { "commentId": 1, "author": "John Doe", "content": "좋은 글이네요!", "createdAt": "2025-02-01T12:00:00Z", "replies": [ { "commentId": 2, "author": "Alice", "content": "저도 그렇게 생각해요!", "createdAt": "2025-02-01T12:05:00Z", "replies": [] } ] }, { "commentId": 3, "author": "Bob", "content": "궁금한 점이 있어요!", "createdAt": "2025-02-01T12:10:00Z", "replies": [] } ] ✅ replies 필드를 이용해 대댓글을 계층적으로 표현. ✅5️⃣ 계층형 댓글 조회의 SQL 방식. 📌1️⃣ 재귀 쿼리(CTE) WITH RECURSIVE comment_tree AS ( SELECT comment_id, parent_id, content, author, created_at FROM comments WHERE article_id = 123 AND parent_id IS NULL UNION ALL SELECT c.comment_id, c.parent_id, c.content, c.author, c.created_at FROM comments c INNER JOIN comment_tree ct ON c.parent_id = ct.comment_id ) SELECT * FROM comment_tree ORDER BY created_at; ✅ 재귀적으로 부모-자식 관계를 조회하여 계층적 데이터를 정렬. ✅6️⃣ 계층형 댓글 조회 API의 성능 최적화. 1️⃣ JPA의 @BatchSize 또는 JOIN FETCH 활용 ➞ N + 1 문제 해결. @Query("SELECT c FROM Comment c JOIN FETCH c c.replies WHERE c.article.articleId = :articleId") List<Comment> findCommentsWithReplies(@Param("articleId") Long articleId); 2️⃣ Redis 캐싱 적용 ➞ 자주 조회되는 댓글 목록을 캐싱하여 성능 향상 가능. ✅7️⃣ 계층형 댓글 조회 API의 활용 사례. 서비스 설명 블로그 블로그 게시글의 계층형 댓글 (ex: 네이버 블로그, 티스토리) 커뮤니티 게시판 댓글 + 대댓글 (ex: 디시인사이드, Reddit) SNS 소셜 미디어 댓글 (ex: 페이스북, 인스타그램) 이커머스 상품 리뷰 대댓글 (ex: 아마존, 쿠팡) ✅8️⃣ 결론. 계층형 댓글 목록 조회는 댓글과 대댓글(답글)을 계층적으로 표시하는 방식. 부모 댓글(parentId)을 저장하여 관계를 유지. Spring Boot + JPA 기반으로 쉽게 구현 가능. 재귀 쿼리(CTE) 또는 JOIN FETCH를 활용하여 최적화 가능.
Backend Development
· 2025-02-08
<
>
Touch background to close