📝느낀 점
이번 프로젝트에서 페이징, 무한 스크롤, 댓글 기능을 구현하며 클라이언트-서버 간 데이터 전달 방식과 JPA의 페이징 로직, 비동기 데이터 처리의 중요성을 이해했습니다. 특히, 백엔드에서 디버깅과 로그 분석의 중요성을 실감하며 데이터 일관성을 유지하는 것이 얼마나 중요한지 배웠습니다. 이러한 경험을 통해 각 계층 간의 데이터 흐름을 명확하게 이해하는 것의 중요성을 깨닫고, 문제 해결 능력과 시스템 안정성을 높이는 방법을 습득할 수 있었습니다. 이번 경험은 복잡한 문제를 체계적으로 분석하고 해결하는 방법을 배우는 소중한 기회가 되었으며, 앞으로의 개발 작업에서 큰 도움이 될 것입니다.
어떻게 했기에 문제 상황을 마주하게 되었는지
Spring Boot와 JPA를 사용해 페이징, 무한 스크롤 댓글 기능을 구현하면서 여러 문제를 마주했습니다.
페이징 기능을 설정할 때 페이지 전환이 되지 않았고, 무한 스크롤 댓글 기능에서는
클라이언트가 서버에서 데이터를 제대로 받지 못하는 상황이 발생했습니다.
댓글 기능 구현에서는 로그인된 유저의 닉네임이 제대로 출력되지 않는 문제가 발생했습니다.
이러한 문제들은 모두 백엔드 로직과 클라이언트 간의 데이터 전달 과정에서 발생한 것이었습니다.
페이징 기능 구현 시 Spring Data JPA의 페이지 번호 계산 로직이 0부터 시작하는 반면,
클라이언트 측에서는 1부터 시작하도록 구현되어 페이지 전환이 제대로 이루어지지 않았습니다.
무한 스크롤 댓글 기능 구현에서는 클라이언트에서 AJAX 요청을 보낼 때
articleId 변수가 제대로 전달되지 않아 서버에서 올바른 데이터를 반환하지 못했습니다.
댓글 기능에서는 로그인된 유저의 정보를 세션에서 가져와서, 사용하지 않아 닉네임이 올바르게 출력되지 않았습니다.
이게 왜 문제인지
페이징 전환이 제대로 이루어지지 않으면 사용자가 다음 페이지의 글을 볼 수 없고,
무한 스크롤 댓글 기능이 작동하지 않으면, 애플리케이션 동작이 제대로 작동하지 않는다는 점입니다.
이는 데이터베이스 쿼리와 서버 응답의 일관성 부족으로 인해 발생하며,
서버와 클라이언트 간의 데이터 전달 과정에서 오류가 생긴 결과입니다.
특히, 댓글 작성 시 로그인된 사용자의 닉네임이 올바르게 설정되지 않으면
데이터 무결성이 깨지고, 세션 관리와 사용자 인증 로직에 문제가 있다는 것을 의미합니다.
이러한 문제들은 애플리케이션을 저하시킬 수 있으며,
사용자들이 웹 애플리케이션을 사용하는 데 있어 불편함을 느끼게 만듭니다.
문제를 어떻게 감지했는지
문제는 주로 브라우저의 개발자 도구를 사용하여 감지되었습니다.
페이지 링크를 클릭했을 때 페이지 전환이 이루어지지 않는 현상을 통해 페이징 문제가 감지되었습니다.
네트워크 요청을 확인한 결과, 페이지 번호가 올바르게 전송되지 않거나
서버에서 요청을 제대로 처리하지 못하는 것을 발견했습니다.
무한 스크롤 댓글 기능에서는 AJAX 요청이 서버에 제대로 도달하지 않거나,
응답을 받았음에도 불구하고 화면에 댓글이 표시되지 않는 문제를 확인했습니다.
또한, 서버 로그와 예외 메시지를 분석하여 문제의 원인을 더 정확하게 파악했습니다.
예를 들어, 특정 엔드포인트에 대한 요청이 실패하거나 예기치 않은 예외가 발생했을 때,
로그를 통해 해당 요청의 세부 내용을 확인했습니다.
데이터베이스 상태를 점검하여 데이터가 예상대로 저장되고 있는지, 쿼리가 올바르게 실행되는지를 확인했습니다.
이를 통해 서버와 데이터베이스 간의 통신에서 발생한 문제를 진단할 수 있었습니다.
로그 파일 분석도 중요한 역할을 했습니다.
각 요청과 응답의 흐름을 추적하면서, 오류가 발생한 지점을 정확히 찾아내고, 그에 따른 원인을 분석했습니다.
이를 통해 프론트엔드와 백엔드 간의 데이터 전달 과정에서 발생하는 문제들을 명확히 할 수 있었습니다.
이러한 종합적인 분석 방법을 통해 페이징, 무한 스크롤, 댓글 구현에서 발생한 문제들을 감지할 수 있었습니다.
어떻게 해결했는지
페이지 번호가 0부터 시작하는 반면, 클라이언트에서는 1부터 시작하도록
구현된 문제를 해결하기 위해 페이지 번호 계산 로직을 수정했습니다.
컨트롤러에서 페이지 번호가 0보다 작지 않도록 설정하고, 클라이언트에서도
페이지 전환 시 올바른 페이지 번호가 전송되도록 JSP 페이지를 수정했습니다.
public Page<ArticleRequestDTO> paging(Pageable pageable) {
int page = Math.max(0, pageable.getPageNumber() - 1);
int pageLimit = 3;
Page<Article> articleEntities = articleRepository.findAll(PageRequest
.of(page, pageLimit, Sort.by(Sort.Direction.DESC, "id")));
return articleEntities.map(article -> new ArticleRequestDTO(
article.getId(),
article.getWriter(),
article.getTitle(),
article.getCreatedAt()));
}
JSP 페이지에서 페이지 번호를 제대로 전송하도록 수정했습니다.
<body>
<h1>게시판 웹 서비스</h1>
<div class="col-md-12">
<table class="table table-horizontal table-bordered">
<tr>
<th>게시글 번호</th>
<th>제목</th>
<th>작성자</th>
<th>최종수정일</th>
</tr>
<c:forEach var="post" items="${articleList}">
<tr>
<td>${post.id}</td>
<td><a href="/articles/update/${post.id}?page=${articlePage.number + 1}">${post.title}</a></td>
<td>${post.writer}</td>
<td>${post.createdAt}</td>
</tr>
</c:forEach>
</table>
<a href="/articles/paging?page=1">첫 페이지</a>
<a href="${!articlePage.first ? '/articles/paging?page=' + (articlePage.number) : '#'}">이전</a>
<c:forEach begin="${startPage}" end="${endPage}" var="page">
<c:choose>
<c:when test="${page eq articlePage.number + 1}">
<span>${page}</span>
</c:when>
<c:otherwise>
<a href="/articles/paging?page=${page}">${page}</a>
</c:otherwise>
</c:choose>
</c:forEach>
<a href="${!articlePage.last ? '/articles/paging?page=' + (articlePage.number + 2) : '#'}">다음</a>
<a href="/articles/paging?page=${articlePage.totalPages}">마지막 페이지</a>
</div>
</body>
</html>
AJAX 요청이 제대로 처리되지 않던 문제를 해결하기 위해 클라이언트 측에서
AJAX 요청 시 필요한 변수를 올바르게 전달하도록 수정하고,
서버에서는 이 요청을 받아 페이지 처리된 댓글 데이터를 반환하도록 했습니다.
스크롤 이벤트를 감지하여 추가적인 댓글을 비동기적으로 로드하도록 구현했습니다.
let page = 0;
let isLoading = false;
function loadComments() {
if (isLoading) return; // 로딩 중이면 추가 요청 방지
isLoading = true;
$('#loading').show();
$.ajax({
url: "/api/v1/article/" + articleId + "/comments?page=" + page,
type: 'GET',
success: function (response) {
const comments = response.content; // 서버 응답에 따라 변경될 수 있음
if (comments.length === 0) {
$('#loading').hide();
return;
}
comments.forEach(comment => {
$('#comment-list table tbody').append(
`<tr id='comment-row-${comment.id}'>
<td id='comment-content-${comment.id}'><span class='bold'>${comment.nickname}</span>: ${comment.contents}</td>
<td class='right-align'>
${comment.createdAt}
<button onclick='editComment(${comment.id})'>수정</button>
<button onclick='deleteComment(${comment.id})'>삭제</button>
</td>
</tr>`
);
});
page++;
isLoading = false;
$('#loading').hide();
},
error: function (xhr, status, error) {
alert("댓글을 로드하는 데 문제가 발생했습니다: " + xhr.responseText);
isLoading = false;
$('#loading').hide();
}
});
}
$(window).scroll(function() {
if ($(window).scrollTop() + $(window).height() > $(document).height() - 100) {
loadComments();
}
});
$(document).ready(function () {
loadComments();
});
댓글 작성 시 로그인된 사용자의 닉네임이 제대로 출력되지 않던 문제를 해결하기 위해,
ArticleComment 엔티티에 작성자의 닉네임을 저장하는 필드를 추가하고,
댓글 저장 시 이 정보를 올바르게 설정하도록 서비스 로직을 수정했습니다.
@Entity
@Getter
@Builder
@AllArgsConstructor
public class ArticleComment extends AuditingFields {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id")
private Article article;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@Column(name = "content", columnDefinition = "TEXT")
private String content;
@Column(name = "writer_nickname", nullable = false)
private String writerNickname;
protected ArticleComment() {}
}
댓글 저장 시 로그인한 사용자의 닉네임을 설정하도록 수정 했습니다.
@Transactional
public ArticleCommentDTO saveComment(ArticleCommentDTO commentDTO, Member member) {
Article article = findArticleById(commentDTO.getArticleId());
ArticleComment commentEntity = articleCommentMapper.toArticleCommentEntity(
commentDTO, member.getNickname(), article, member);
ArticleComment savedComment = articleCommentRepository.save(commentEntity);
return articleCommentMapper.toArticleCommentDTO(savedComment);
}
그렇게 하면 왜 해결되는지
페이지 번호 불일치 문제를 해결함으로써 클라이언트와 서버 간의 페이지 전환이 일관되게 이루어지게 되었으며,
사용자가 원하는 콘텐츠를 정확히 확인할 수 있게 되어 탐색 경험이 크게 개선되었습니다.
또한, AJAX 요청과 응답의 흐름을 정리함으로써 무한 스크롤 기능이 원활하게 작동하게 되었고,
스크롤할 때마다 필요한 데이터만을 비동기적으로 로드함으로써 서버의 리소스 사용이 최적화되었습니다.
추가적으로, 댓글 작성자의 닉네임이 올바르게 저장되고 출력되도록 개선된 결과, 데이터의 무결성이 보장되었고,
사용자가 자신의 활동 기록을 정확하게 확인할 수 있게 되었습니다.
이러한 데이터 일관성은 시스템의 신뢰성과 보안성에도 사용자 세션 관리와 인증 로직이 강화되었습니다.
결과적으로, 이러한 수정 작업은 시스템의 성능과 안정성을 크게 개선하였습니다.
얼마나 개선되었는지
시스템에 접근하여 데이터를 수정하는 경우에도 데이터 무결성을 크게 보완 되었으며,
클라이언트와 서버 간의 페이지 번호 불일치 문제를 해결하고 페이징 기능을 정상적으로 동작 시켰습니다.
사용자가 원하는 데이터를 정확하게 요청하고 받아볼 수 있도록 함으로써
서버의 응답 속도와 리소스 효율성이 개선되었습니다.
또한, 무한 스크롤 댓글 기능을 최적화하여 불필요한 데이터 요청을 줄이고
필요한 데이터만을 비동기적으로 로드하게 함으로써 서버 부하를 감소시켰습니다.
댓글 작성 시 로그인된 사용자의 닉네임이 정확하게 저장되고 출력되도록 개선하여
사용자 데이터의 일관성을 유지하고 개인화된 사용자 경험을 제공하게 되었습니다.
이와 함께, 서버 로깅 및 예외 처리 시스템을 강화하여 발생할 수 있는 문제를 빠르게 감지하고
대응할 수 있는 능력을 가짐으로써, 운영 환경에서의 안정성을 높이고 유지보수를 용이하게 하여,
결과적으로 전체 시스템의 성능, 안정성, 그리고 사용자 경험이 전반적으로 향상되었다고 생각합니다.
배우는 것은 무엇인지
비동기 통신의 중요성을 깊이 이해하게 되었으며, 특히 AJAX 요청을 통해 클라이언트와 서버 간의
데이터를 비동기적으로 처리하고, 실시간으로 사용자에게 데이터를 제공하는 방법을 익혔습니다.
이 과정에서 서버와 클라이언트 간의 데이터 일관성을 유지하는 것이
시스템 안정성과 사용자 편의성에 큰 영향을 미치는지를 체감하게 되었고,
이를 위해 페이징 로직을 정확하게 구현하고 관리하는 것이 중요하다는 사실을 배웠습니다.
또한, 서버에서 발생하는 다양한 예외 상황을 적절히 처리하고, 이를 통해 시스템 운영 환경에서
발생할 수 있는 잠재적 문제들을 사전에 예방하고 해결할 수 있는 능력을 배양하게 되었습니다.
이러한 경험은 단순히 기술적인 문제 해결에 그치지 않고, 시스템 전체의 안정성과 유지보수성을
생각해보는 시각을 가지게 해주었습니다. 앞으로의 개발 과정에서 더 나은 결정을 할 수 있도록
도와주는 중요한 밑거름이 될 것이라고 생각합니다.
무엇을 얻을 것인지
데이터 무결성을 유지하고 사용자 데이터를 안전하게 처리하는 방법을 배우면서,
사용자의 민감한 정보를 보호하고 시스템의 신뢰성을 유지하는 것이 얼마나 중요한지를 깨달았습니다.
또한, 클라이언트와 서버 간의 데이터 흐름을 보다 명확하게 이해하게 되어, 데이터 일관성을 유지하면서도
성능을 저하시키지 않는 솔루션을 설계하고 구현할 수 있는 능력을 갖추게 되었다고 생각합니다.
이를 통해 더욱 견고하고 신뢰할 수 있는 시스템을 구축하는 데 기여할 수 있는 역량을 얻었으며,
앞으로도 지속적으로 학습하고 개선해 나갈 수 있는 기반을 마련하게 되었습니다.
이 방법이 최선이였는지
이 방법은 주어진 상황에서 최선의 해결책이었다고 생각합니다.
페이지 번호 불일치 문제는 클라이언트와 서버 간의 데이터 일관성을 유지하기 위해 필수적으로 해결해야 했습니다.
AJAX를 활용한 무한 스크롤 기능도 사용자 경험을 개선하는 데 매우 효과적이었습니다.
다른 방법으로는 더 복잡한 기술을 도입할 수 있었겠지만,
현재 선택한 방법이 성능과 유지보수성을 모두 고려한 최선의 선택이었다고 생각합니다.
특히, 클라이언트와 서버 간의 상호작용을 간소화하면서도 문제를 해결할 수 있었기 때문에,
이 방법은 문제 해결에 있어 적절한 접근법으로 선택하게 되었습니다.
다른 방법은 없었는지
WebSocket을 사용하여 클라이언트와 서버 간의 지속적인 연결을 유지하면서 실시간 업데이트를 구현할 수도 있습니다.
이는 사용자에게 즉각적인 피드백을 제공하는 데 매우 유용할 수 있지만,
서버 리소스를 더 많이 소모하게 되며, 구현의 복잡성이 증가합니다.
특히, 실시간 데이터 전송이 필수적이지 않은 경우에는 필요 이상의 부담을 줄 수 있습니다.
현재의 해결 방법은 이러한 대안들에 비해 상대적으로 간단하면서도 효율적이며,
성능과 유지보수성을 고려했을 때 가장 적절한 선택이었습니다.
클라이언트와 서버 간의 데이터 일관성을 유지하면서도 사용자 경험을 크게 향상시킬 수 있었기 때문에,
다른 방법에 비해 최선의 선택이었다고 할 수 있습니다.
'프로젝트(project)' 카테고리의 다른 글
JWT 로그인 및 Spring Security 인증 문제 (0) | 2024.07.31 |
---|---|
조회수 및 좋아요 기능 구현과 문제 해결 과정 (0) | 2024.07.29 |
JPA 엔티티 Dirty Checking 오류 해결 (0) | 2024.07.29 |
HTTP 415 오류 해결 과정 (회원가입 + 로그인) (0) | 2024.07.28 |
닉네임 중복 검증과 예외 처리 (0) | 2024.07.28 |