1. 학습 주제
인증 기능 위에 사용자 조회 기능을 추가하면서, 명령과 조회를 분리하는 CQRS 방식을 조금 더 구체적으로 다뤘다.
회원가입, 로그인처럼 데이터를 변경하는 작업과 사용자 정보 조회처럼 데이터를 읽는 작업을 나눠서 구성하고, 조회 쪽은 MyBatis를 사용해 처리하는 흐름을 정리했다.
- Command와 Query를 분리하는 이유
- 사용자 본인 정보 조회 API
- 관리자 전용 사용자 목록 조회 API
- @AuthenticationPrincipal로 로그인 사용자 정보 꺼내기
- @PreAuthorize를 이용한 메서드 단위 권한 제어
- JPA와 MyBatis를 함께 사용하는 구조 이해
2. CQRS란 무엇인가
CQRS는 Command Query Responsibility Segregation의 줄임말이다.
말 그대로 데이터를 변경하는 책임과 조회하는 책임을 분리하는 방식이다.
Command
- 데이터를 생성, 수정, 삭제하는 작업
- 예: 회원가입, 비밀번호 변경, 사용자 삭제
Query
- 데이터를 읽는 작업
- 예: 내 정보 조회, 사용자 목록 조회
이 둘을 나누는 이유는 역할이 다르기 때문이다.
- 변경 작업은 정합성과 트랜잭션이 중요하다.
- 조회 작업은 빠르고 필요한 형태로 데이터를 가져오는 것이 중요하다.
그래서 보통
- Command는 JPA
- Query는 MyBatis나 JPQL, QueryDSL
처럼 다르게 가져가는 경우가 많다.
3. 왜 조회를 따로 분리하는가
회원가입 같은 기능은 엔티티 중심으로 다루는 것이 자연스럽다.
하지만 조회는 꼭 엔티티 전체가 필요하지 않을 때가 많다.
예를 들어 사용자 목록을 보여줄 때
- 아이디만 필요할 수도 있고
- 권한만 필요할 수도 있고
- 상세 페이지가 아니라면 비밀번호나 불필요한 컬럼은 필요 없다
이럴 때 조회 전용 DTO로 필요한 값만 꺼내오면 더 효율적이다.
즉, 조회는 “저장 구조”보다 “보여줄 구조”가 더 중요하다.
4. 조회 API 흐름
구현 흐름은 다음처럼 정리할 수 있다.
- 클라이언트가 사용자 조회 API를 요청한다.
- 컨트롤러가 요청을 받는다.
- 서비스가 조회 로직을 수행한다.
- 서비스는 MyBatis Mapper를 호출한다.
- Mapper가 SQL을 실행한다.
- 조회 결과를 DTO로 받아 응답 객체에 담아 반환한다.
이 구조는 이전에 만든 로그인 흐름과 연결된다.
로그인이 끝난 뒤에는 JWT 인증 필터가 인증 정보를 SecurityContext에 넣어주고,
조회 API에서는 그 인증 정보를 꺼내서 “지금 로그인한 사용자가 누구인지” 알아낸다.
5. 내 정보 조회와 @AuthenticationPrincipal
내 정보 조회 API에서는 @AuthenticationPrincipal을 사용했다.
이 어노테이션은 Spring Security가 보관 중인 인증 객체에서
현재 로그인한 사용자의 정보를 꺼내 파라미터로 넣어준다.
즉, 별도로 username을 다시 요청받지 않아도 된다.
동작 흐름은 다음과 같다.
- 로그인 후 AccessToken을 발급받는다.
- 요청 시 헤더에 AccessToken을 담아 보낸다.
- JWT 필터가 토큰을 검증한다.
- 토큰에서 username을 꺼낸다.
- 사용자 정보를 불러와 인증 객체를 만든다.
- 이 인증 객체가 SecurityContext에 저장된다.
- @AuthenticationPrincipal이 그 안의 사용자 정보를 꺼낸다.
결국 @AuthenticationPrincipal은
“로그인한 사람 기준으로 조회해야 하는 API”에서 매우 자주 쓰인다.
예를 들면
- 내 정보 조회
- 내 주문 조회
- 내 장바구니 조회
같은 기능에 자연스럽게 연결된다.
6. 관리자 전용 조회와 @PreAuthorize
사용자 목록 조회는 관리자만 가능하도록 제한했다.
여기서 사용한 것이 @PreAuthorize다.
@PreAuthorize("hasAuthority('ADMIN')")
이 의미는 메서드가 실행되기 전에
현재 인증된 사용자가 ADMIN 권한을 가지고 있는지 먼저 검사하겠다는 뜻이다.
즉,
- 권한이 있으면 메서드 실행
- 권한이 없으면 실행 전에 차단
된다.
이 방식은 URL 설정만으로는 부족할 때 특히 유용하다.
예를 들어
- 어떤 URL은 같지만 파라미터에 따라 권한 체크가 다를 수 있고
- 메서드별로 더 세밀하게 통제하고 싶을 수 있다
이럴 때 메서드 단위 보안이 더 직관적이다.
7. @PreAuthorize와 URL 보안 설정의 차이
Spring Security에서는 보통 두 군데에서 보안을 건다.
1. URL 단위 보안
- 특정 URL 패턴에 대해 접근 허용/제한
- 예: /admin/**는 관리자만 허용
2. 메서드 단위 보안
- 실제 메서드 실행 전 조건 검사
- 예: @PreAuthorize("hasAuthority('ADMIN')")
URL 보안은 전체적인 출입문 같은 느낌이고,
메서드 보안은 세부 방 안쪽 잠금장치 같은 느낌이다.
실무에서는 둘을 같이 쓰는 경우가 많다.
8. 조회 전용 DTO를 사용하는 이유
조회에서 엔티티를 그대로 반환하지 않고 DTO를 따로 둔 이유도 중요하다.
엔티티를 그대로 반환하면 생길 수 있는 문제
- 불필요한 필드가 함께 노출될 수 있다
- 응답 구조가 DB 구조에 종속된다
- 화면 요구사항이 바뀔 때 유연하게 대응하기 어렵다
DTO를 사용하면 좋은 점
- 필요한 값만 담을 수 있다
- 응답 구조를 명확하게 제어할 수 있다
- API 응답과 DB 저장 구조를 분리할 수 있다
조회는 특히 “무엇을 저장했는가”보다
“무엇을 보여줄 것인가”가 더 중요하기 때문에 DTO 분리가 더 의미 있다.
9. MyBatis를 같이 사용하는 이유
이번 구성은 변경 작업은 JPA, 조회 작업은 MyBatis로 나눠 이해하기 좋았다.
JPA가 잘 맞는 경우
- 저장
- 수정
- 삭제
- 엔티티 중심 처리
- 트랜잭션 중심 작업
MyBatis가 잘 맞는 경우
- 복잡한 조회
- 원하는 컬럼만 조회
- SQL을 직접 보면서 제어하고 싶을 때
- 화면 맞춤형 응답이 필요할 때
즉, 둘 중 하나만 무조건 써야 하는 것이 아니라
목적에 따라 더 잘 맞는 도구를 선택하는 것이 핵심이다.
10. Mapper와 XML의 역할
MyBatis에서는 Mapper 인터페이스와 XML이 함께 동작한다.
Mapper 인터페이스
- 자바에서 호출할 메서드를 정의
- 예: 사용자 한 명 조회, 사용자 목록 조회
XML
- 실제 SQL 작성
- 어떤 컬럼을 어떤 조건으로 조회할지 명시
이 구조는 SQL이 눈에 보여서 이해하기 쉽다.
특히 조회 쿼리가 복잡해질수록 “무슨 SQL이 실행되는지” 명확하게 드러난다.
11. 설정에서 확인한 포인트
조회 기능을 붙이면서 설정도 같이 바뀌었다.
ddl-auto: update
- 실행할 때마다 테이블을 새로 만드는 방식이 아니라
- 기존 구조를 유지하며 변경분만 반영하는 쪽으로 설정
MyBatis 설정
- 카멜 케이스 매핑 지원
- 매퍼 XML 위치 지정
- DTO 패키지 별칭 설정
이런 설정은 “기능 코드”는 아니지만 실제로는 매우 중요하다.
설정이 맞지 않으면 Mapper가 아예 동작하지 않거나, 컬럼 매핑이 꼬일 수 있다.
12. 인증과 조회가 연결되는 방식
이번 흐름에서 중요한 건 “인증이 끝나야 조회도 가능하다”는 점이다.
예를 들어 내 정보 조회는 다음처럼 이어진다.
- 로그인 성공
- AccessToken 발급
- 요청 시 Authorization 헤더에 토큰 포함
- JWT 필터가 토큰 검증
- 인증 객체 생성
- 컨트롤러에서 @AuthenticationPrincipal로 사용자 정보 사용
- 사용자명 기준으로 조회 실행
즉, 조회 기능이 따로 있는 것처럼 보여도
실제로는 앞단의 인증 구조 위에서 동작하는 기능이다.
13. 핵심 정리
- CQRS는 데이터를 변경하는 작업과 조회하는 작업을 분리하는 방식이다.
- 변경은 JPA, 조회는 MyBatis처럼 역할에 따라 기술을 나눌 수 있다.
- 조회에서는 엔티티보다 DTO 중심 설계가 더 유리한 경우가 많다.
- @AuthenticationPrincipal은 현재 로그인한 사용자 정보를 꺼낼 때 사용한다.
- 내 정보 조회는 로그인한 사용자 기준으로 동작하는 대표적인 예시다.
- @PreAuthorize는 메서드 실행 전에 권한을 검사하는 방식이다.
- 관리자 전용 기능은 메서드 단위 보안으로 더 명확하게 제어할 수 있다.
- MyBatis는 필요한 컬럼만 조회하거나 SQL을 직접 제어하고 싶을 때 강점이 있다.
- 인증 구조와 조회 구조는 분리되어 보이지만 실제로는 서로 연결되어 있다.
- 보안, 조회 DTO, SQL 분리까지 함께 고려해야 API 구조가 더 깔끔해진다.
'TIL > [TIL]' 카테고리의 다른 글
| [TIL]Python 개발 환경과 기본 자료형 (0) | 2026.07.13 |
|---|---|
| [TIL]코드 컨벤션 재정리와 AWS RDS 기반 DB 서버 확인 (0) | 2026.06.26 |
| [TIL]Spring Security와 JWT 기반 인증 흐름 (0) | 2026.06.23 |
| TIL - Spring Security 기초, 세션과 토큰, Filter와 Interceptor, SSR과 CSR (0) | 2026.06.22 |
| [TIL]Spring Data JPA로 메뉴 등록, 수정, 삭제 구현하기 (0) | 2026.06.18 |