Back-end/Spring Boot

Back-end/Spring Boot

@ConfigurationProperites 이용한 프로퍼티 객체 관리

What? 이전에는 @Value 를 사용해 *.yml , *.properties 의 프로퍼티 값들을 가져왔습니다. 개인적으로는 기능에 문제가 없었기 때문에 개선의 필요성을 인지하지 못하고 있었는데, 지인분의 추천으로 @ConfigurationProperties 과 @ConstructorBinding 어노테이션을 알게 되어 공유합니다. Why? 기존의 문제점부터 짚고 넘어가겠습니다. 아래와 같은 yml이 있다고 가정합니다. server: port: 8080 domain: http://localhost:8080 동적 타입 문제 @Value("${server.port}") private String stringPort; @Value("${server.port}") private Integer integerPo..

Back-end/Spring Boot

[Spring Boot] logback으로 CloudWatch에 서버 요청 로깅하기

💡 발단 앱을 런칭하기 전부터 불안한 점이 있었다. "만약 서버가 터진다면... 어떻게 해결하지?". 그래서 생각한게 로깅이었다. 로깅이 모든 문제를 해결할 수는 없겠지만, 문제를 해결하는데 필요한 결정적인 실마리를 줄 수 있다고 생각했다. 적어도 비정상적인 요청 혹은 서버내의 에러를 파악할 수 있다고 생각해서 바로 적용하게 됐다. 물론 서버에 SSH 접속해서 직접 로그를 확인하는 방법도 있다. 그래서 해당 프로젝트에선 docker를 사용해서, docker log 명령어를 사용해서 확인해왔다. 그러나 만약 서버가 죽는다면? SSH 접속 자체가 안된다면 원인을 파악할 방법이 없어지게 된다. 외부의 어딘가에 로그들을 저장해둘 필요가 있었다. 그래서 찾아본게 AWS CloudWatch. 공식적으로 로그 기능을..

Back-end/Spring Boot

[Spring JPA] 페이지네이션 일대다 FETCH JOIN 문제와 default_batch_size

💡발단 HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory WARN이 반복적으로 나오는 걸 보게 됐다. 이때까지만 해도 ERROR은 아니니까 적당히 나중에 리팩토링해야겠다고 생각했다. 그러나 쿼리문을 자세히 보니 큰 문제가 있다는걸 깨달았다. 실제 쿼리를 날릴 때 limit 을 걸지 않는다. 알고보니 DB에서 모든 row를 가져온 뒤에 메모리상에 올려두고 처리하는 방식이었다. 아래는 Querydsl 코드. location_bookmark는 location에 대한 일대다 엔티티이다. location_bookmark를 레프트 조인하고 fetch join하면서 발생하는 부분이 핵심이다. public JPA..

Back-end/Spring Boot

[Spring JPA] 엔티티 컬렉션 필드를 초기화해야 하는 이유

💡 발단 발단 부분은 Java적인 문제이고, 본론은 이 다음에 나오니 건너뛰시길 추천드립니다. 프로젝트를 진행하다가 엔티티 생성자쪽에서 에러가 발생했다. Builder 생성자에서 엔티티 컬렉션 필드를 할당하는 쪽에서 생긴 문제인데, 너무 당연하게 생각했어서 더 많은 생각을 하게 됐다. Spring 프로젝트를 2번 하는 동안 컬렉션 필드를 생성자에서 만들어본 적이 없었던 것. 상황은 이렇다. 컬렉션 필드를 addAll()로 할당하는 부분에서 NPE(Null Pointer Exception)가 발생한 것. 문제는 NULL값이 들어갈 때였다. // ... 필드 private List feedImages = new ArrayList(); @Builder private Feed(String title, Strin..

Back-end/Spring Boot

[Spring Boot] 커서 페이징(no offset)에서 Page 대신 Slice 사용하기

💡발단 우리 프로젝트의 프론트엔드는 모바일 안드로이드로, 대부분의 조회 페이지가 무한 스크롤로 구현된다. 즉, 오프셋이 쓰이지 않고 커서를 이용해 페이징한다. 마지막으로 응답했던 레코드의 id 값을 커서(커서 아이디)로 사용한다. 맨 처음엔 커서 아이디를 0으로 요청 -> 아이디 1~10을 갖는 레코드를 응답하고, 그 다음엔 커서 아이디를 10으로 요청 -> 아이디 11~20 을 갖는 레코드를 응답하는 식이다. 이 상황에서 조회 기능들을 구현하면서 배우게 된 Page의 단점과 Slice의 장점을 서술하고자 한다. ❗Page, Pageable의 대부분의 필드는 커서 페이징에 필요하지 않다. 애초에 커서 페이징에는 offset이 쓰이지 않는다. offset이 없으므로 getOffset, getPageNumb..

Back-end/Spring Boot

[Spring JPA] Hibernate에서 지원하지 않는 MySQL 랜덤 함수 직접 만들기 + Expressions 파헤쳐보기

상황 설명 현재 프로젝트에는 환경 퀴즈를 푸는 서비스가 있고, 유저가 제출한 퀴즈를 복습하는 기능이 있다. 특이한 점은 복습 퀴즈들을 랜덤으로 보여줘야 한다는 것. 즉 문제들의 정렬 방식을 랜덤으로 두어서 유저가 요청할 때마다 다른 퀴즈를 봐야한다. 발단 문제는 랜덤 정렬을 구현하면서 발생했다. @Override public Slice findRecentQuizzes(Long userId, int limit) { JPAQuery query = queryFactory.selectFrom(quiz) .where(...) .orderBy(NumberExpression.random().asc()) // 이 부분 // ... } 발생한 에러: org.springframework.dao.InvalidDataAcce..

Back-end/Spring Boot

[Spring Boot] 개발 환경 분리와 ddl-auto 재앙 방지 + @Profile

💡발단 현재 프로젝트에선 기존에 application.yml 설정 파일 하나에 모든 설정 정보를 넣어놓고 있었다. 가장 큰 문제는 로컬에서 적용하던 설정을 실제 배포된 인스턴스에 적용하는 휴먼 에러가 발생할 수 있다는 것. 그리고 가장 무서운건 ddl-auto의 존재다. 유명한 개발바닥 재난급 장애 영상 만약 실서비스에서 ddl-auto : create로 실행을 한다면... DB가 날아가고 복구하는데 너무 큰 비용이 생기게 된다. 그 외에도 개발 환경을 분리하면 환경별로 설정을 관리할 수 있게 되기 때문에 편리한 부분이 많다. 스프링은 공식적으로 profile(프로파일) 기능을 지원한다. 🛠 프로파일 적용 적용 방법은 간단하다. 인텔리제이에서는 서버 Edit Configuration -> Active P..

Back-end/Spring Boot

[Spring Boot] @Valid 유효성 검사 (jakarta validation)

💡 유효성 검사가 필요한 이유 1. 컨트롤러 레이어에서 Request Body 검사를 함으로써 API 명세를 정확히 지킬 수 있음 2. 프론트엔드 개발자의 개발중 실수를 즉각적으로 알려줌 3. 비즈니스 로직상 올바르지 않은 형태의 데이터가 DB에 저장되는 것을 방지함 사용 방법 의존성 추가 (Spring Boot 3.0, Java 17) dependencies { // ... implementation 'org.springframework.boot:spring-boot-starter-validation:3.0.5' validation 종류 validation 종류 참고 : https://jeongkyun-it.tistory.com/130 개인적으로 주로 사용하는 어노테이션의 종류와 사용 팁은 다음과 같다..

Back-end/Spring Boot

[Spring Boot] 연관관계 생성 메서드 삽질

💡 발단 엔티티 생성자에서 정말 기본적인걸 놓치고 있었다. 1:N (일대다) 관계의 두 엔티티가 있었고, 나는 자식쪽에서 부모 엔티티만 연결해주고 있었다. 마치 레코드를 INSERT할 때 FK만 넣어서 참고하는 것처럼 말이다. 기존의 코드: 객관식 문제(Quiz)와 그 선택지(Choice)이다. 중요한 부분 외엔 생략, 단순화시켰다. // 문제쪽의 생성자 @Builder public Quiz(String properties, List choiceList) { this.properties = properties this.choiceList = choiceList; } // ... // 선택지쪽의 생성자 @Builder public Choice(String properties, Quiz quiz) { thi..

Back-end/Spring Boot

[Spring JPA] 실시간으로 적재되는 데이터와 부모 엔티티 묶어서 가져오기

💡 발단 프로젝트에서 맞닥뜨린 요구사항은 이렇다. 엔티티로는 로케이션이 있고, 각 로케이션에 대해서 서울시 인구 혼잡도 API로부터 5분마다 혼잡도 데이터를 적재한다. ex. 홍대입구역(id=3)에 대한 9시의 인구혼잡도가 2레벨(기획상 1~3으로 나뉜다)이라면 congestion(location_id=3, congestion_level=2, observed_at=2023-08-05 18:35:43.769817) 으로 저장된다. 나는 각 location별로 가장 최근의 혼잡도 (congestion)을 조회해야 했다. 아래 사진은 와이어프레임인데, 각 카드 하단의 '여유', '복쟉', '보통'이 각 로케이션의 가장 최근 혼잡도 레벨이다. '지하철'은 location_category에 해당. 💦 삽질 과정 ..

zorbathegeek
'Back-end/Spring Boot' 카테고리의 글 목록