다국어 다루기
·
트러블슈팅
1. Locale 우선순위를 어떻게 줄 것인가?처음에는 스프링이 기본 제공하는 AcceptHeaderLocaleResolver만으로 충분해 보였다. 하지만 몇 가지 요구가 추가됐다.관리자 화면에서는 URL 파라미터로 언어를 강제로 지정하고 싶다.백오피스에서는 API Gateway가 붙이는 헤더(X-Accept-Language)를 우선으로 삼아야 한다.사용자가 마지막으로 선택한 언어를 쿠키에 저장했다가 재방문 시 적용하고 싶다.이 요구를 만족시키려면 Resolver 하나로는 부족했다. 그래서 Chain of Responsibility 패턴을 차용해 Resolver들을 순차적으로 시도하도록 CompositeLocaleResolver를 만들었다.@Beanfun localeResolver( supportedL..
Gradle에서 task.named vs tasks.withType의 차이
·
트러블슈팅
최근 Gradle 멀티모듈 프로젝트를 구성하다가 이상한 현상을 마주했다. Spring Boot의 bootJar 작업을 활성화했는데 JAR 파일이 전혀 생성되지 않는 문제였다.처음 설정은 이랬다:tasks.withType { enabled = true}tasks.withType { enabled = false}이 설정은 JAR 파일은 생성되지 않았다.문제가 발생한 이유이 문제는 Gradle이 Task를 설정하는 방식과 시점에 있다. Gradle은 Task를 설정할 때 두 가지 방법을 제공한다:tasks.withType: 즉시(eager) 설정 적용tasks.named("taskName"): 나중에(lazy) 설정 적용tasks.withType은 설정을 즉시 적용하는 방식인데, 이로 인해 Grad..
JPA, 트랜잭션, IDENTITY, SEQUENCE 전략에 대한 오해와 이해
·
트러블슈팅
개발 중 JPA의 쿼리 동작을 보면서, 나는 몇 가지 이상한 점을 겪었다.INSERT만 한 줄 알았는데 UPDATE가 나가고, INSERT는 찍혔는데 DB에는 없고… 내가 잘못 알고 있었던 점과 JPA가 실제로 동작하는 방식을 정리한 것이다.@Transactionalpublic boolean a(Eco2 eco2) { Eco2 save = eco2Repository.save(eco2); // INSERT b(save.getId()); return true;}public boolean b(Long id) { Eco2 eco2 = eco2Repository.findById(id).get(); // SELECT? eco2.update(Loca..
NoOpServerSecurityContextRepository 와 ReactiveSecurityContextHolder 차이
·
트러블슈팅
처음에 NoOpServerSecurityContextRepository 설정을 보고 “SecurityContext는 더 이상 저장되지 않겠구나”라고 생각했다.stateless한 JWT 기반 인증 구조를 만들기 위해 이 설정을 사용한 것이었다.하지만 디버깅 중에 이상한 현상을 발견했다.ReactiveSecurityContextHolder.withAuthentication(...)를 통해 인증 정보를 저장했더니,인증 정보도 필터 체인 내부에서 정상적으로 동작하고 있었다.“아니, 분명히 context 저장을 막았는데 어떻게 인증 정보가 유지되는 거지?”이 의문으로부터 NoOpServerSecurityContextRepository와 ReactiveSecurityContextHolder의 역할을 명확히 구분해서..
spring batch reader
·
트러블슈팅
기존의 ItemReader가 아닌, InfluxDB에서 데이터를 받아서 저장하는 커스텀 ItemReader를 직접 구현해야 하는 상황이라, Spring Batch의 실행 흐름과 Reader의 동작 방식을 확실히 이해하고 가기로 했습니다. Spring Batch 실행 흐름 개요Spring Batch의 실행 흐름은 다음과 같이 Job → Step → Tasklet 구조로 진행됩니다.1. Job 실행 (`JobLauncher.run()`) ├──> Step 실행 (`TaskletStep.doExecute()`) ├──> 트랜잭션 시작 (`doInTransaction()`) ├──> Chunk 단위 데이터 처리 (`ChunkOrientedTasklet.execute()`) ..