전체 글 169

Spring Data JPA에서 조회 개수를 제한하는 방법: Pageable과 int 비교

채팅 기능을 구현하면서 최신 메시지를 일정 개수만 조회하기 위해 다음 코드를 사용했다.public List getRecentMessages(int size) { Pageable pageable = PageRequest.of(0, size); return repository.findRecentMessages(pageable) .stream() .map(ChatMessageResponse::new) .toList();}Repository는 다음과 같다.@Query(""" select m from ChatMessage m join fetch m.sender order by m.id desc """)List find..

개발공부/JPA 2026.09.08

WebSocket 메시지 흐름을 구조화하는 STOMP

앞서 간단한 실습으로 순수 WebSocket을 구현하면서 서버가 연결된 세션을 직접 관리하고, 반복문을 통해 모든 사용자에게 메시지를 전송하는 과정을 확인했다.하지만 채팅방, 개인 메시지, 전체 알림처럼 메시지 종류가 많아지면 다음과 같은 정보까지 직접 관리해야 한다.누가 메시지를 보냈는가?어느 채팅방으로 보내야 하는가?어떤 사용자가 메시지를 받아야 하는가?사용자는 어떤 채팅방에 참여하고 있는가?WebSocket은 실시간 통신이 가능한 연결 통로만 제공하기 때문이다. 이러한 메시지 흐름을 일정한 규칙으로 관리하기 위해 사용하는 프로토콜이 STOMP다.STOMP란 무엇인가?STOMP는 Simple Text Oriented Messaging Protocol의 약자로, WebSocket 연결 위에서 메시지를 ..

개발공부/WEB 2026.09.07

WebSocket이란? Spring Boot로 간단한 실시간 통신 구현하기

일반적인 HTTP 통신에서는 클라이언트가 요청해야 서버가 응답할 수 있다.클라이언트 → 요청 → 서버클라이언트 ← 응답 ← 서버예를 들어 새로운 채팅이나 알림이 있는지 확인하려면 클라이언트가 서버에 반복해서 요청해야 한다.클라이언트: 새로운 메시지가 있나요?서버: 없습니다.클라이언트: 지금은 있나요?서버: 없습니다.클라이언트: 지금은요?서버: 있습니다.이 방식은 새로운 데이터가 없어도 요청을 계속 보내야 한다는 단점이 있다.WebSocket을 사용하면 클라이언트와 서버가 한 번 연결된 후 그 연결을 계속 유지한다.클라이언트 ↔ WebSocket 연결 유지 ↔ 서버연결된 상태에서는 클라이언트뿐만 아니라 서버도 원하는 시점에 메시지를 보낼 수 있다. 이처럼 양쪽이 자유롭게 데이터를 주고받는 방식을 양방향 전..

개발공부/WEB 2026.09.04

동시성 문제와 락: 비관적 락, 낙관적 락, 분산 락 이해하기

서비스에 사용자가 많아지면 여러 요청이 같은 데이터를 동시에 변경하는 상황이 발생한다.대표적인 예가 다음과 같다.동일한 계좌에서 동시에 출금한정된 상품의 재고 동시 차감포인트 동시 적립과 사용선착순 쿠폰 발급동일한 게시글을 여러 사용자가 동시에 수정이러한 상황에서는 단순히 데이터를 조회하고 수정하는 것만으로는 데이터의 정확성을 보장하기 어렵다.1. 동시성이란?동시성은 여러 요청이 같은 시간대에 동일한 데이터에 접근하는 상황을 의미한다.서버가 여러 대일 때만 발생하는 문제가 아니다. Spring Boot 서버가 한 대여도 여러 요청을 서로 다른 스레드에서 동시에 처리하기 때문에 동시성 문제는 발생할 수 있다.계좌 출금 예시계좌 잔액이 10,000원이고 두 요청이 동시에 1,000원씩 출금한다고 가정해 보자..

DB 인덱스 제대로 이해하기: B+Tree부터 복합 인덱스와 EXPLAIN까지

데이터가 적을 때는 인덱스의 효과를 체감하기 어렵다. 하지만 데이터가 수십만 건, 수백만 건으로 늘어나면 원하는 행을 찾기 위해 테이블 전체를 읽는 작업은 큰 비용이 된다.이때 필요한 것이 인덱스(Index)다.인덱스는 테이블에 새로운 칼럼을 추가하는 것이 아니라, 특정 칼럼의 값을 빠르게 찾기 위해 DB가 별도로 관리하는 탐색 자료구조다.1. 인덱스는 단순한 정렬표가 아니다MySQL의 InnoDB 인덱스는 일반적으로 B+Tree 계열 구조로 관리된다.인덱스는 컬럼 값을 정렬해서 보관할 뿐만 아니라, 트리의 분기를 따라 원하는 값이 있는 위치까지 빠르게 이동할 수 있게 한다. [50] / \ [20] [70] / \..

캐시는 왜 필요할까? Spring Cache와 Redis 제대로 이해하기

1. 캐시란 무엇인가?캐시는 자주 사용하는 데이터를 더 빠른 저장 공간에 임시로 보관하는 것이다.일반적인 데이터 조회 흐름은 다음과 같다.클라이언트 요청 ↓Spring Boot 서버 ↓데이터베이스 연결 ↓SQL 실행 ↓조회 결과 반환캐시를 적용하면 먼저 캐시에 데이터가 있는지 확인한다.클라이언트 요청 ↓캐시 조회 ├─ 데이터가 있음 → 캐시 데이터 반환 └─ 데이터가 없음 → DB 조회 → 캐시에 저장 → 결과 반환캐시에 데이터가 있는 경우를 Cache Hit, 없는 경우를 Cache Miss라고 한다.Cache Hit요청 → 캐시 조회 성공 → 바로 응답DB를 조회하지 않기 때문에 응답 속도가 빠르고 DB 부하도 줄어든다.Cache Miss요청 → 캐시에 데이터 없..

개발공부/Redis 2026.09.01

HandlerMethodArgumentResolver와 NativeWebRequest 이해하기

Spring의 컨트롤러를 작성하다 보면 메서드의 매개변수에 @RequestParam, @PathVariable, @RequestBody, HttpServletRequest, HttpSession 등을 선언하기만 해도 Spring이 알아서 값을 전달해준다.@GetMapping("/members/{id}")public MemberResponse getMember( @PathVariable Long id, @RequestParam String name, HttpServletRequest request) { // ...}개발자는 id, name, request 객체를 직접 생성하지 않았다. 그런데도 컨트롤러가 호출될 때는 각각의 값이 정상적으로 전달되는 이유는 Spri..

카테고리 없음 2026.08.05

코딩 테스트에서 중간값을 변수로 분리해야 하는 이유

들어가며코딩 테스트를 풀다 보면 식 자체는 거의 맞는데, 상태(State)를 끝까지 추적하는 능력이 부족해서 정답을 맞히지 못하는 경우가 있었다. https://school.programmers.co.kr/learn/courses/30/lessons/132267최근 콜라 문제를 풀면서도 비슷한 실수를 했다.처음에는 현재 가진 빈 병의 개수를 n이라고 두고, a개의 빈 병을 주면 b개의 콜라를 받을 수 있다는 조건을 다음과 같이 구현했다.answer += (n / a) * b;n = n / a + n % a;첫 번째 줄에서는 (n / a) * b를 이용해 새로 받은 콜라의 개수를 정확하게 계산했다. 하지만 다음 반복에서 사용할 빈 병의 개수를 갱신할 때는 n / a만 사용했다. 이때 n / a는 받은 콜라..

코딩테스트 2026.08.04

Spring Security에서 왜 Entity의 User가 아닌org.springframework.security.core.userdetails.User를 사용해야 할까?

Spring Security를 학습하면서 한 가지 의문이 생겼다.“어디에서는 내가 작성한 User를 가져다가 쓰고, 어디에서는 spring에서 제공하는 User를 써야하는 것일까?”나 역시 게시글 작성 기능을 구현하던 중 다음과 같은 오류를 만나면서 그 이유를 명확하게 이해하게 되었다.발생한 오류java.lang.NullPointerException: Cannot invoke "org.example.advance.common.entity.User.getUsername()" because "user" is nullat org.example.advance.domain.post.controller.PostController.createPost(PostController.java:24)처음에는 뭐가 문제인지 감..

🤦🏻‍♀️ 메서드가 몇 개 없는데도 공통 메서드를 분리해야 할까?

프로젝트를 진행하면서 Service 클래스 하나에 메서드가 4개 정도 있었다.각 메서드에서 동일한 로직이 반복되고 있었지만, 처음에는 굳이 private 공통 메서드로 분리하지 않았다.솔직히 메서드가 몇 개 되지도 않는데 오히려 메서드만 하나 더 늘어나는 것 같아 비효율적이라고 생각했기 때문이다.예를 들면 아래와 같은 코드였다.public CustomerResponse findCustomer(Long id) { Customer customer = customerRepository.findById(id) .orElseThrow(() -> new CustomException(...)); ...}public CustomerResponse updateCustomer(Long id,..