본문 바로가기

Programming/Java & Spring 관련 내용 정리

(20)
Push Server "개선" 작업을 해보다. 얼마 전 회사에서 기존 푸시서버 개선을 진행했다. 기존 푸시 서버의 문제점은인증정보 기반의 타겟 푸시 발송에 국한되어 있는 포맷이라서비스성 및 이벤트성 메세지 발송을 하기에는 비효율적이었다. (ex) 보내려는 서비스성 푸시메세지가 기존 시스템의 포맷과 맞지 않아 새로운 DB 테이블을 생성하거나새로운 객체에 맞게 발송 프로그램을 따로 구현하고 테스트 해야 했음 한 마디로 확장성과 유연성이 떨어지는 구조였다.  이를 해결하기 위해 인증 기반 푸시 메세지 뿐 아니라다양한 서비스 유형 푸시 메세지를 발송할 수 있도록 DB와 코드를 재설계했다. 개선 전에는 새로운 유형의 푸시를 보낼 때기존 포맷에 맞춰 추가 개발하고 테스트하는데 하루 정도 시간이 소요되었으나개선된 시스템에서는 스펙만 정해지면 대상 쿼리, 발송 시..
MDC를 활용해 쓰레드 전환을 이해해보다. 멀티쓰레드 환경에서는 항시적으로 로그가 뒤섞이곤 한다. 여러 요청이 동시에 처리될 때동일한 요청에 대한 로그가 연속적으로 쌓이는것이 아니기 때문이다.  이 문제를 해결하는 한 가지 방법으로, MDC(Mapped Diagnostic Context)를 활용할 수 있다. 각 HTTP 요청에 대한 UUID를 MDC에 저장하고, 이 UUID를 모든 로그 메시지에 자동으로 포함시키는 방식이다.이를 통해 로그를 분석할 때 각 요청을 명확하게 구분할 수 있다. 이처럼 MDC는 주로 로그에 추가적인 컨텍스트 정보를 자동으로 포함시키는 기능을 제공한다.덕분에 멀티스레드 환경에서도 요청별 또는 사용자별로 로그를 일관되게 관리할 수 있어로그 추적이 훨씬 간편해진다.    MDC를 이용해 특정 로그를 제외시키는 것은 일반적인 ..
JPA에서 @BatchSize 를 쓸 것인가 vs Fetch Join을 쓸 것인가 [ JPA에서 @BatchSize 사용 시 장점 ] @BatchSize 어노테이션은 Hibernate에서 성능을 개선하는 데 유용하다.특히 대량의 데이터를 처리할 때 이 어노테이션의 효과를 직접 느낄 수 있다.  상황cust_info (사용자 정보) 테이블을 조회할 때,대부분의 상황에서 cust_agree_info (사용자 동의 정보)도 함께 조회해야 해야 하는 상황.예를들어 1,000명의 회원 정보를 한꺼번에 조회 할 때는 어떨까? JPA 기본 쿼리 방식기본적으로 Hibernate는 1000명의 cust_info를 조회한 후,각 회원의 cust_agree_info를 조회하기 위해 별도의 쿼리를 실행함.결과적으로 1000개의 쿼리가 데이터베이스로 나가게 되며,이로 인해 성능 저하를 초래하는 N+1 문제가..
[Java] 인터페이스를 통해 if문의 향연을 고쳐보았다. 상황 물건의 최소 시세값이 '5억', 최대 시세값이 '10억' 이라고 세팅 되었다고 할 때, - 사용자의 물건이 7억이면? 필터링을 통과한다. 👉 5억과 10억 사이기 때문이다. - 사용자의 물건이 3억이면? 필터링을 통과하지 못한다. 👉5억과 10억 사이가 아니기 때문이다. 이에 대한 필터링 코드를 작성했고, 이를 리팩토링 하는 과정을 정리해보고자 한다. 결론 비교 화면 리팩토링 전 코드 딱 봐도 if 문의 향연 그 잡채다. 최소값과 최대값이 항상 둘 다 세팅된다는 보장이 없기에 (1) 최소값 / 최대값이 둘 다 세팅되었을 때 (2) 최대값만 세팅되었을 때 (3) 최소값만 세팅되었을 때 를 나누어 조건을 분리하는 그런 코드이다. 필터링을 수행하는데 크게 이상은 없지만, if 문이 너무 많아 보기 좋아보..
[Java] 역직렬화로 JSON 포맷을 바꿔보았다. 백오피스 프로젝트를 개발하던 중에 Jackson 라이브러리를 통해 '역직렬화'를 사용해 보았다. 먼저 상황은 다음과 같았다. 프로모션 관련 백오피스 기능이었는데, 프로모션을 등록할 때 섹션 종류가 총 3가지가 있었다. (buttion, image, detail) 이 때 프론트에서 세팅해야하는 JSON 데이터 포맷을 맨 처음에는 아래 이미지에서 A와 같이 되도록 코드를 작성했었다. sectionType이 달라짐에 따라 "imageSection" , "buttonSection", "detailSection" 과 같은 필드가 따로 따로 생기는 방식이었다. 프론트 입장을 고려했을 때는 A 보다는 B 방식이 데이터를 다루는 데 있어서 더 편하다고 판단하였고 B 와 같이 JSON 포맷이 되도록 하기 위해 Jacks..
[Java] 인터페이스 사용하여 필터링 기능을 구현해 보았다. 최근에 했던 개발 중,  특정 상품에 대해 필터링을 해야 하는 내용이 있었다. 내용을 간단히 말하면,현재 이 상품을 사용하려는 사용자가 가진 조건이이 상품이 요구하는 조건과 일치하지 않는다면 필터링 아웃 시켜야하는 것이었다.   제일 처음 작성했던 코드 스타일은 다음과 같다. @Slf4j@Service@Transactional@RequiredArgsConstructorpublic class CalculatorService {private boolean filterConditions(CalculatorContext context, long productSeq) { return Stream.of( isPurposeValid(putData.getPurposeCd(), p..
[Spring] 멀티 쓰레드 환경에서 자원이 공유돼 버렸다. 이번에는 어떤 상황이 발생했을까?! 최근에 특정 데이터를 스크래핑하는 개발을 했다.  Java + Selenium 을 통해 서버에서 크롬창을 연 뒤 클릭이벤트를 발생시켜 데이터를 스크래핑 한 후,그 데이터를 Redis에 저장하는 방식이었다.  AWS SQS (Simple Queue Service) 기술도 사용했다."C"라는 서버에서는 SQS 메세지를 Publishing 하는 역할을 하고=> "D"라는 스크래핑 서버에서는 AWS SQS를 구독하고 있다가 메세지가 생기면 수신하여 처리하는 방식이다.   이때 "D"라는 스크래핑 서버에는 아래와 같은 서비스가 존재한다. (1) SqsService (큐 서비스)(2) ScraperService (스크래퍼 서비스)  (1) SqsService(큐 서비스)는 SQS..
[Spring] @Async는 왜 제대로 동작하지 못했는가악! 일단, @Async가 뭘까? @Async는 Spring에서 제공하며, Thread Pool을 활용하여비동기를 지원하는 어노테이션이다.   @Async가 제대로 동작하지 않는 상황이 발생했다! 이번에도 어김없이 등장하신 개발실 실장님 🙌 최근 누군가가 개발한 코드 중에서@Async 어노테이션을 붙인 코드가 제대로 동작하지 않아서비동기 처리가 되고 있지 않은데원인이 무엇인지 확인해 보라고 하셨다.   회사코드라 그대로 옮길 수 없지만,대략 큰 뼈대 내용은 다음과 같다.   public class BenzService extends CarService{ public void init(CarDto carDto) { // 생략 .. callExternalApi(carDto, api..
[Spring] Thread-safe 에 대해서 고찰해 보다. 부제 : 스프링 빈은  멀티쓰레드 환경에서 Thread-safe 할까?  개발 실장님께서아래 코드에 버그가 있는데, 무엇이 버그인지 알겠냐고 물어보셨다. ( 회사코드를 그대로 가져다 쓸 수 없어 예시를 들기 위해 클래스명, 변수명 등을 조금 변경했다. )   아래 코드를 보면,for문을 돌면서 getBean() 메소드를 통해 생성되어 있는 스프링 빈(bean)을 찾아와 characterService에 할당하고 있다.                                       - (1)번 파트  그다음 할당된 스프링 빈(bean)의 init() 메소드를 실행한다.   - (2)번 파트  public class Service { // .... 생략 private CharacterServi..
[코드리뷰 받은 내용 정리] 코드를 작성할 때 '이것'을 생각해보자! 🎨 참조타입 vs 원시타입 @Schema(description = "정기알림 신청여부", example = "false") private Boolean monthlyRequestYn; [ 위 코드에 대한 코드리뷰 내용 ] " 개인적으론 Boolean type은 boolean을 쓰는 걸 추천합니다. Reference type과 성능 차이도 있을 수 있지만 null point exception 과 같은 것도 고민안해도 되니깐요. 특히 boolean type은 만약 입력을 하지 않으면 false나 true 둘 중에 하나의 default 값이 존재하는 경우가 많아서 reference type을 쓸일이 더 없었던거 같아요. 물론 100%라고 말씀드릴 순 없어요. 그러나 여기는 false가 default 일거 같긴..
[Spring] 멀티모듈 구성 방법 ✏️ (예시) 아래와 같이 모듈 프로젝트를 구현해야 하는 상황일 때 관리자 API 모듈, 스케쥴러 API 모듈, 서비스 모듈을 구성해야 함 관리자 API 모듈 : 게시판 CRUD / 회원 CRUD / 로그인 담당 (controller) 서비스 모듈 : service, mapper 등 스케쥴러 API : 껍데기만 생성 🎯프로젝트 생성방법, pom.xml 구성 방법 먼저 전체를 아우르는 ‘basic-module’ 을 아래와 같이 new Project로 생성한다. 2. 새로 만든 ‘basic-module' 프로젝트에 아래와 같이 관리자 API 모듈 ( = admin) 을 추가한다. 스케쥴러 API 모듈 ( = schedule)도 동일한 방법으로 추가한다. 3. ‘basic-module’ 프로젝트에 서비스 모듈..
[Spring Batch] 오류가 나면 어떻게 해야할까? - Skip과 Retry 스프링 배치에서는 Skip이라는 기능을 통해 Step 내에서 발생한 특정 Exception에 대해 해당 Exception을 n번까지 허용하겠다는 설정을 할 수가 있다. 예를들어 skip(NotFoundNameExcpetion.class).skipLimit(3) 으로 설정한 경우 NotFoundNameExcpetion 발생 3번까지는 에러를 skip 한다. NotFoundNameExcpetion 발생 4번째부터는 Job과 Step의 상태가 실패로 끝나며, 배치가 중지된다. 단, 에러가 발생하기 전까지 데이터는 모두 처리된 상태로 남는다. Step은 chunk 1개 기준으로 Transaction이 동작한다. 예를 들어 items = 100, chunk.size = 10 일 때 총 chunk 동작 횟수는 10..
[Spring Batch] 스프링 배치를 써야 할까? 휴면회원 상태변경 스프링 배치를 통해 스프링배치를 구현하면서 이런 의문점이 생겼습니다. [Spring Batch] 로그인한지 1년 지난 회원 -> 휴면회원으로 상태변경하는 배치구현 [ 실행흐름 ] 1. [ Job] Job은 스프링부트 배치를 처리함에 있어서 가장 상위 계층이다. JobBuilderFactory에서 생성된 JobBuilder를 통해 Job을 생성한다. ✏️ .incrementer(new RunIdIncrementer()) Spring.. domean.tistory.com ‘솔직히 이렇게 스프링 배치로 긴 코드를 구현할 것 없이 update user set status = 'inactive' where login_date > 2022-01-01 라는 업데이트 쿼리 날리는 API 를 스케쥴러로..
[Spring Batch] Step에서 데이터를 처리하는 방법 2가지 (+ 예제) 스프링배치의 Step 단계에서 데이터를 처리하는 방법은 2가지가 있다. 1. Chunk를 사용한 Step 2. Tasklet을 사용한 Step 이 2가지 방법의 차이점을 아래와 같이 알아보았다. 🎈예제 상황 100개의 문자열을 list로 만들고, list 사이즈를 로그로 찍는 Task 1. Chunk를 사용한 Step @Bean public Step chunkBaseStep() { return stepBuilderFactory.get("chunkBaseStep") .chunk(10) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) .build(); } private ItemReader itemReader() { return ..
[Spring Batch] 로그인한지 1년 지난 회원 -> 휴면회원으로 상태변경하는 배치구현 오늘 기준으로 마지막 로그인 일자가 1년이 넘은 회원의 사용자 상태를 ‘ACTIVE’ 👉 ‘INACTIVE’ 상태로 변경하는 배치를 만들었다. * 전체 소스코드는 하단에 있습니다. :) [ 스프링 배치 실행흐름 ] 코드로 살펴보면 다음과 같다. 1. [ Job] Job은 스프링부트 배치를 처리함에 있어서 가장 상위 계층이다. JobBuilderFactory에서 생성된 JobBuilder를 통해 Job을 생성한다. ✏️ JobBuilderFactory는 JobBuilder를 생성할 수 있는 get() 메소드를 포함하고 있다. get()메소드는 "inactiveUserJob"이라는 Job을 만들어 반환한다. ✏️ .incrementer(new RunIdIncrementer()) Spring Batch에서 전..
[Spring] Filter와 Interceptor의 차이가 무엇인가요? (+ AOP) 개발을 하다보면 공통적으로 처리해야 할 업무들이 있다. 예를들면 세션체크처리, 권한체크, pc와 모바일웹의 분기처리, 페이지 인코딩 변환 등이 있다. 공통업무에 관련된 코드를 모든 페이지 마다 작성 해야한다면 중복된 코드가 많아지고 프로젝트 단위가 커질수록 서버에 부하를 줄 수도 있으며 소스 관리도 복잡해질 수 있다. 이 때문에 공통 부분은 빼서 따로 관리하는게 좋다. 공통업무를 프로그램 흐름의 앞, 중간, 뒤에 추가하여 자동으로 처리할 수 있는 방법은 아래와 같이 3가지가 있다. 1. Filter 2. Interceptor 3. AOP 3가지 방식의 특징 및 차이점은 아래와 같다. Filter Interceptor AOP 적용시점 Spring Context 외부 Spring Context 내부 ~ Co..
[Spring] @Transactional이 내부적으로 어떻게 동작하나요? 🎈@Transactional 의 작동원리, 흐름 Spring에서는 클래스, 인터페이스, 메소드에 부여할 수 있는 @Transactional이라는 어노테이션을 제공하고 있다. @Transactional이 붙은 메소드를 호출하면, Spring은 해당 메소드에 대한 프록시를 만든다. 트랜잭션은 트랜잭션의 시작과 연산 종료시의 커밋 과정이 필요하므로 프록시를 생성해 해당 메서드의 앞뒤에 트랜잭션의 시작과 끝을 추가하는 것이다. 프록시 객체는 @Transactional이 포함된 메서드가 호출될 경우, 트랜잭션을 시작하고 Commit 또는 Rollback을 수행한다. CheckedException이 발생하거나 예외가 없을 때는 Commit을 진행하고, UncheckedException이 발생하면 Rollback을 ..
[Spring] @Component와 @Bean의 차이가 뭔가요? 스프링은 개발의 제어권이 스프링 컨테이너(IoC 컨테이너)에 있다고 한다. 그래서 이를 IoC(Inversion Of Control), 제어의 역전이라고 한다. 스프링이 개발자 대신 객체를 제어하기 위해서는 객체들이 빈(Bean)으로 등록되어 있어야 한다. 과거에는 객체를 빈으로 등록하기 위해 XML로 지정했어야 한다고 하는데, 요즘엔 어노테이션으로 간단하게 등록할 수가 있다. 스프링에서 빈으로 등록하는 방법은 무엇일까? 스프링 MVC에서는 @Controller, @Service, @Repository 등으로 빈으로 등록할 수 있으며, configuration 관련 객체들은 @Bean과 @Component 으로 스프링 컨테이너에 객체를 빈으로 등록할 수 있다. @Bean 과 @Component의 차이는 ..
[JAVA] String 클래스에 대해 설명해 주세요. 🎈 스트링 내부를 들여다보면? 스트링 내부를 살펴보면, Char 배열로 생성된 불변객체 값이다. 🎈 Java에서 String은 불변(Immutable) 객체이다. 불변 객체란, 객체가 생성된 후 내부의 상태가 변하지 않고 계속 유지되는 객체를 말한다. 변수에 객체가 한 번 할당되면 해당 객체의 참조를 변경할 수도, 내부의 상태를 수정할 수도 없는 것이다. 🎈 왜 String은 불변 객체일까? 1. String Pool String이 불변이기 때문에 String Pool도 존재할 수 있다. 어떤 프로그래밍 언어라도 String 타입은 매우 빈번하게 사용된다. 그래서 Java에서는 String Pool이라는 공간에 String을 포함시켜서 매번 String 객체를 새로 생성하기보다는 값이 같은 String이..
[JAVA] GC, stop-the-world, G1GC에 대해서 설명해 주세요. 📍Garbage Collector 동적으로 할당한 메모리 영역 중 사용하지 않는 영역을 탐지하여 해제하는 기능 📍 Stop-the-world 가비지 컬렉터를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것 Stop-the-world가 발생하면 가비지 컬렉터를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘다. 가비지 컬렉터 작업을 완료한 이후에 중단했던 애플리케이션 실행을 다시 시작한다. 어떤 가비지 컬렉터 알고리즘을 사용하더라도 stop-the-world는 발생한다. 대개의 경우 가비지 컬렉터 튜닝이란 stop-the-world 시간을 줄이는 것이다. 📍Young 영역(Yong Generation 영역) 새롭게 생성한 객체의 대부분이 여기에 위치한다. 대부분의 객체가 금방 접근 불가능 ..