외부 API를 연동하다 보면 여러 메서드에서 비슷한 형태의 try-catch가 반복되는 경우가 있다.YouTube Data API를 호출하는 과정에서 IOException을 처리하는 코드가 여러 메서드에 반복됐다.try { return youtubeApiRequest.execute();} catch (IOException e) { throw new YoutubeApiException("유튜브 API 요청에 실패했습니다.", e);}처음 한두 개의 메서드에서는 크게 문제라고 느끼지 않았다.하지만 API 호출 메서드가 늘어나면서 각 메서드의 핵심 로직보다 반복되는 예외 처리 코드가 더 눈에 들어오기 시작했다.이번 글에서는 반복되는 try-catch를 공통 메서드로 분리하고, checked exce..
모던 자바 인 액션 8장: 8.2.1 removeIf 메서드 파트를 읽다가 for-each 문을 사용한 예시에서 list의 요소를 제거할 때 ConcurrentModificationException이 발생할 수 있다는 것을 알게 되었다.책에서 에러가 발생하는 이유는 반복자의 상태와 컬렉션의 상태가 동기화되지 않아서라고 설명하며 해결책에 대해서 설명해준다. 나는 동기화되지 않는 이유가 궁금하여 이 부분에 대해서 좀 더 찾아보았다.ConcurrentModificationException이 발생하는 이유에러가 발생하는 코드for (Transaction transaction : transactions) { // 예외가 발생하는 지점 if(Character.isDigit(transaction.getRefer..
List 계층 구조 Collection 인터페이스에는 boolean remove(Object o) 메서드가 정의되어 있다.List 인터페이스에는 E remove(int index) 메서드가 정의되어 있다. 여기서 의문이 들었던 것은 아래와 같은 상황에서 어떤 메서드가 호출되는가 하는 것이었다.List numbers = new ArrayList(Arrays.asList(1, 2, 3, 4, 5));numbers.remove(1); // 이건 인덱스 1을 삭제하는 걸까? 값 1을 삭제하는 걸까?System.out.println(numbers); // 출력: [1, 3, 4, 5] 정답은 인덱스 1번 요소가 삭제된다. 즉, 2가 삭제된다.왜냐하면 자바의 오버로딩 선택은 정확히 일치하는 타입의 메서드를 우선으..
선착순 쿠폰을 발급하는 상황에서 데드락 문제와 동시성 문제를 해결한 과정과 성능 개선 과정에 대해서 글을 작성해보겠다. 1. 요구 사항이벤트 시작 시 수천 명에서 수만 명이 동시에 쿠폰을 요청하는 상황요구 사항 : 쿠폰 1000개 한정, 1인 1매 발급2. 데드락 문제 해결 과정아래 테이블 구조는 보기 편하게 기존 테이블 구조에서 불필요한 칼럼은 제외했다. (username과 name도 필요없긴 하다.)회원이 쿠폰 발급 버튼을 클릭하면 coupons 테이블에 있는 쿠폰 수량이 줄어들고, user_coupons 테이블에 발급한 쿠폰이 추가된다. 하지만 이 구조에서는 데드락이 걸리는 문제가 있다.쿼리문으로 상황을 살펴보도록 하자.-- transaction ABEGIN; -- 1INSERT INTO user..
기존 잉크 브릿지 프로젝트는 생일 쿠폰을 매월 1일 00:00에 배치로 발급하는 기능이 구현되어 있었고,이 로직은 팀원 분께서 Spring Batch 기반으로 개발했다. 이 기능의 요구 사항은 다음과 같았다.사용자가 생일인 달 1일 자정에 생일 쿠폰 제공예) {5월 1일, 5월 5일, 5월 20일} 생일자 : 5월 1일에 쿠폰 제공쿠폰 사용 기간은 해당 월의 1일부터 말까지여기서 나는 실제 이커머스(ex. 무신사?) 운영 환경을 고려해 다음과 같은 조건을 가정하고 요구사항을 확장했다.전체 회원 수는 약 2,400만 명각 달의 생일자는 약 200만 명씩 균등 분포그리고 개발된 코드를 리뷰하던 중, 성능 측면에서 개선이 필요한 부분들이 눈에 띄었고, 이를 기반으로 배치 처리 로직을 리팩토링하게 되었다.1. ..
InkBridge 프로젝트를 리팩토링하던 중, 스프링 배치를 적용해야 할 시점이 왔었다.그래서 스프링 배치 공부를 5버전으로 했는데 스프링 부트 3 이상에서만 이 버전을 지원한다는 것을 알았다.원래는 전반적인 리팩토링을 끝낸 뒤 마이그레이션을 진행하려고 했지만, 배치를 5버전으로 공부한 김에 지금 계획했던 마이그레이션을 하기로 했다. 마이그레이션 대상은 다음과 같다.Java 11 -> Java 17Maven -> GradleSpring Boot 2.7 -> Spring Boot 3.4현재 스프링 부트의 가장 최신 버전인 3.4 버전으로 마이그레이션 하기로 했고, 그에 따라 자바 버전도 17이상으로 올려야 했다.그리고 기존 빌드 도구는 Maven이었는데 이 팀 프로젝트를 진행한 이후로는 Gradle만 사용..
문제 - 두 큐 합 같게 만들기 프로그래머스SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프programmers.co.kr처음 해결 방법import java.util.*;class Solution { public int solution(int[] queue1, int[] queue2) { Queue q1 = new ArrayDeque(); Queue q2 = new ArrayDeque(); long sum1 = 0; long sum2 = 0; for (long v : queue1) { q1.offer(v); sum1 += v; ..
문제 - [1차] 비밀지도처음 해결 방법class Solution { public String[] solution(int n, int[] arr1, int[] arr2) { String[] answer = new String[n]; int[] map = new int[n]; for(int i=0; i 처음에는 다음과 같은 흐름으로 문제를 해결했다.각 행을 비트연산(OR)으로 병합한다.병합된 값을 2진수 문자열로 변환한다.1을 #으로 변환하고, 0을 " "으로 변환한다.변환한 문자열이 n자리보다 짧으면 앞에 " "을 추가해 길이를 맞춘다.문제점 발견이 방법은 동작은 하지만 비효율적인 부분이 있었다.replace()는 내부적으로 문자열 전체를 순회하므로 시간복잡도 ..
save()와 saveAll()이 같은 저장 기능을 수행하지만 성능에는 차이가 있고 @Transactional 사용 여부에 따라서도 다른 결과를 나타낸다. 이번 글에서는 이 둘의 내부 구조와 함께, @Transactional 유무에 따른 성능 차이를 실험과 함께 분석해보겠다.1. save()와 saveAll() 내부 구조JpaRepository의 기본 구현체인 SimpleJpaRepository의 내부를 보면 다음과 같다. save()@Transactional@Overridepublic S save(S entity) { Assert.notNull(entity, "Entity must not be null."); if (entityInformation.isNew(entity)) { ..
기존에 구현한 카테고리 로직에서는 N+1 문제, 재귀적 구조의 한계, 조회 효율성 저하 같은 문제점들이 있었다.그래서 이번 글에서는 카테고리 리팩토링 과정을 담아보도록 하겠다.1. 기존 구조의 문제점1.1 인접 리스트 모델(Adjacency List Model)기존 구조는 인접 리스트 모델로, Category 엔티티 내에 자기 자신을 참조하는 CategoryParent, CategoryChildren 필드를 통해 재귀적으로 자기 자신을 참조했다.1.2 N+1 문제 발생readAllCategory() 메서드에서 부모 카테고리를 조회한 뒤, 각 카테고리의 자식들을 DTO로 변환하는 과정에서 지연 로딩으로 인해 매번 추가 쿼리가 발생했다. 여기서는 @BatchSize설정을 사용한다고 하더라도 완전한 최적화는 ..
댓글이나 카테고리 등 계층 구조를 가진 데이터를 표현할 때 보통 테이블을 재귀적으로 설계한다.하지만 계층 구조를 만드는 방식은 여러 가지가 있으며, 각각 장단점이 존재한다.이번 글에서는 대표적인 계층 구조 테이블 설계 방식들에 대해 알아보겠다.재귀구조를 띄는 인접리스트 모델중첩 집합 모델(Nested Set Model)클로저 테이블Materialized Path1. 인접리스트 모델 (Adjacency List Model)1.1 구조 설명가장 널리 사용되는 방식으로 각 데이터가 자신의 부모 ID를 참조한다.SQL로는 자기 참조 외래키를 통해 표현한다.CREATE TABLE category ( id BIGINT PRIMARY KEY, name VARCHAR(100) NOT NULL, parent_id ..
1. 상황과 문제점예시 Entitypublic class CategoryEntity extends BaseEntity { ... @Column(name = "name", nullable = false, length = 20) private String name; ... @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_id") private CategoryEntity parentCategoryEntity; ...}예시 쿼리 메소드public interface CategoryJpaRepository extends JpaRepository { boolean existsByParentCategoryEn..