보안, 유지보수성, 그리고 동시성 제어
이번 글에서는 프로젝트의 품질을 한 단계 높이기 위해 진행했던 리팩토링 과정을 공유합니다. 데이터베이스 접속 정보를 안전하게 보호하는 암호화 작업부터, 프론트엔드 코드의 중복을 제거하는 모듈화, 그리고 빈번한 데이터 갱신 상황에서의 동시성 제어 처리 방법을 소개합니다.
1. Jasypt를 활용한 민감 정보 암호화
개발을 진행하다 보면 데이터베이스 URL, 사용자명, 비밀번호와 같은 민감한 정보가 application.yaml 파일에 평문으로 노출되는 경우가 많습니다. 이는 보안상 큰 취약점이 될 수 있기에 Jasypt (Java Simplified Encryption) 라이브러리를 도입하여 해결했습니다.
기존에 널리 쓰이던 PBEWithMD5AndDES 알고리즘은 현재 기준으로는 보안 강도가 충분하지 않습니다. 따라서 저희는 PBEWITHHMACSHA512ANDAES_256 알고리즘을 적용하여 보안성을 대폭 강화했습니다. 또한, 암호화 키(Master Key)는 소스 코드 내부에 두지 않고 실행 시점의 환경 변수나 VM 옵션으로 주입받도록 설정하여, 코드가 유출되더라도 복호화가 불가능하도록 구성했습니다.
테스트 환경에서도 이 설정을 동일하게 유지하기 위해 ReflectionTestUtils를 활용합니다. JasyptConfig의 설정을 테스트 코드에서 재사용함으로써 암호화 및 복호화 정합성을 보장할 수 있습니다.
2. Thymeleaf Fragment를 이용한 뷰 모듈화
게시물 목록(index.html)과 상세 페이지(viewer.html)를 개발하다 보면 게시물을 보여주는 UI 코드가 필연적으로 중복됩니다. 이러한 중복은 디자인 변경 시 여러 파일을 수정해야 하는 번거로움을 낳고 유지보수성을 저하시킵니다. 이를 해결하기 위해 Thymeleaf의 Fragment 기능을 활용하여 공통 부분을 모듈화했습니다.
-
postitem.html: 목록에서 사용되는 게시물 박스 컴포넌트 -
postview.html: 상세 페이지의 본문 영역 컴포넌트 -
post/common.html: 좋아요, 리트리(가지치기), 공유 버튼 등 하단 공통 영역
UI뿐만 아니라 handleLike와 같은 자바스크립트 함수 역시 별도의 외부 JS 파일로 분리했습니다. 결과적으로 UI나 로직 수정이 필요할 때 한 곳만 변경하면 모든 페이지에 즉시 반영되는 효율적인 구조를 갖추게 되었습니다.
3. JPA를 활용한 동시성 이슈 해결 및 성능 최적화
3.1. 원자적(Atomic) 업데이트를 통한 동시성 제어
‘좋아요’ 수나 ‘브랜치’ 수처럼 빈번하게 업데이트되는 필드는 동시성 문제가 발생하기 가장 쉬운 지점입니다. Java 애플리케이션 레벨에서 값을 조회하고 1을 더해 저장하는 방식(entity.setCount(count + 1))은 동시에 여러 요청이 유입될 경우 데이터 유실(Lost Update)로 이어집니다.
이 문제를 해결하기 위해 JPA의 @Modifying 어노테이션을 사용하여 데이터베이스 레벨에서 직접 UPDATE 쿼리를 실행하도록 변경했습니다.
UPDATE post SET like_count = like_count + 1 WHERE id = ?
이 방식은 데이터베이스의 락(Lock) 메커니즘을 자연스럽게 활용하여 데이터의 무결성을 보장합니다. 주의할 점은 업데이트 쿼리 실행 후 postRepository.save(entity)를 호출하면 안 된다는 것입니다. 영속성 컨텍스트에 남아있던 변경 전의 값이 DB의 최신 값을 덮어쓸 위험이 있기 때문입니다.
3.2. 메서드 이름 규칙을 활용한 명확한 쿼리 생성
자식 게시물을 조회하는 로직에서는 JPA의 메서드 이름 규칙 중 Property Traversal을 적극 활용했습니다.
findAllByParentPost_Id: 언더바(_)는 아주 중요한 역할을 합니다.Post엔티티 내부의parentPost객체를 탐색하고, 그 안의id필드를 조건으로 사용한다는 것을 JPA에게 명확히 전달합니다. 이를 통해 필드명이 겹치거나 복잡한 연관 관계에서도 모호성 없는 쿼리를 생성할 수 있습니다.
3.3. 필요한 컬럼만 조회하기 (Projection)
단순히 좋아요 개수(Long) 하나가 필요한 상황에서 엔티티 전체를 조회하는 것은 불필요한 리소스 낭비입니다. 이를 최적화하기 위해 @Query를 사용하여 필요한 필드만 콕 집어 조회하도록 변경했습니다.
@Query("SELECT p.likeCount FROM Post p WHERE p.id = :id")
Long findLikeCountById(Long id);
JPA의 기본 메서드인 findBy...는 엔티티 전체 조회를 수행하므로, 특정 타입의 반환값이 필요할 때는 위와 같이 명시적인 쿼리 작성이 필수적입니다.
이번 리팩토링으로 애플리케이션의 보안성을 확보하고, 중복 코드를 제거하여 유지보수 효율을 높였습니다. 무엇보다 동시성 환경에서도 데이터가 정확하게 관리될 수 있는 튼튼한 기반을 마련했습니다.
링크:
링크: » 일본어로 보기 (日本語で見る)
링크: » 영어로 보기 (Switch to English)
공유: