묻고 답해요
164만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
Remote Partitioning
킬구형 6장 작전3에 첫번째 예제 Manager 노드 실행 명령까지 전체 코드를 볼 방법이 있어?
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
킬구형 실무에서는 JPA ItemReader / ItemWriter를 더 선호해? 아니면 JDBC itemReader/ItemWriter를 더 선호해?
킬구형, 히사시 부리데쓰요야 JPA ItemReader와 ItemWriter를 배워보니 생각보다 복잡하고 여러 고려사항들이 많이 보이는 것 같아, 물론 JPA와 통합이 되서 좋지만 JPA를 쓰고 있어도 Jdbc ItemReader/ItemWriter가 훨씬 더 간단해보이고 별다른 추상화도 많이 없어서 성능도 괜찮아 보이는데, 실무에서는 어때? JPA ItemReader / ItemWriter를 더 많이 써 Jdbc ItemReader/ItemWriter를 더 많이써? PS: 킬구형 어떻게 이렇게 자세한 내용들을 알 수 있는거야? spring batch 공식 docs를 봐도 내용이 없던데 ㅋㅋㅋ 킬구형 없었으면 큰일 날뻔했자너~
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
CouponService 의존성 의문
CouponService쪽에선 다른 Service와 다르게 Implement Layer를 의존안하고, DataAccess Layer의 Repository 계층에 의존하고 있던데, 왜 그런걸까요?
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
상품 목록 조회 고도화 질문
상품 목록 조회가 복잡해질 때의 상황을 생각해보고 있는데요. 감이 오지 않는 부분들이 몇가지 있어 질문드립니다. 상품 목록 조회 시 옵션 개념이 있다면 재고는 어떻게 처리해야 할까요? 재고는 상품옵션들의 조합이 가지고 있는 것이 이상적일 것 같은데, 상품 목록 조회던, 상세 조회던 품절 여부와 최대 구매가능 수량을 나타내기 위해서는 재고 정보도 필요해보입니다. 따라서 Product 필드에 해당 상품의 전체재고수량(모든 조합의 실재고 합)을 필드로 가지고 있는게 맞나? 라는 생각이 들고... 필드로 가지고 있다면 상품 조회 시 조합을 전부 조회하고 매번 합해 주어야 하나? 그럼 Product 테이블 컬럼단에 미리 합한 값을 넣어두어야 하나? 여러 생각이 듭니다. 다음으로는 프로모션(타임세일) 적용가, 쿠폰적용가 등으로 보여줘야 한다면 이게 실질적으로 Product 도메인 객체의 가격을 변경해도 되는 건가? 라는 생각도 했습니다.프로모션(타임세일) 적용가, 쿠폰적용가 등으로 보여줘야 한다면 쿠폰적용가, 타임세일 적용가 등이 처음에는 보여주기만을 위한 정보이지 않을까 생각했는데, 실제 주문할 때 반영될 금액이기도 하고, 타임세일 같은 경우는 대부분 모두에게 적용되어 보이는 형태이기에... 뷰의 목적이 아니라 개념적으로 봐도 되는걸까? 라는 생각이 듭니다.조금 고도화 하여 생각해보니 감이 잡히지 않는 부분들이 너무 많네요,,, 실무에서는 주로 어떤 구조를 추구하는지 제미니님은 어떻게 생각하시는지 궁금합니다. 긴 글 읽어주셔셔 감사드려요!
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
병렬 중첩
형 병렬 중첩 시키는 예제 코드있잖아.Worker Step Multithreading Implementation@Bean public Step managerStep(Step workerStep) { return new StepBuilder("managerStep", jobRepository) .partitioner("workerStep", dailyTimeRangePartitioner) .step(workerStep) .taskExecutor(partitionTaskExecutor()) .gridSize(4) .build(); } @Bean public Step workerStep(SynchronizedItemReader<TestLog> redisLogReader, ItemProcessor<TestLog, TestLog> logProcessor, MongoItemWriter<TestLog> mongoLogWriter) { return new StepBuilder("workerStep", jobRepository) .<TestLog, TestLog>chunk(500, transactionManager) .reader(redisLogReader) .processor(logProcessor) .writer(mongoLogWriter) .taskExecutor(workerTaskExecutor()) .build(); } @Bean @StepScope public SynchronizedItemReader<TestLog> redisLogReader(@Value("#{stepExecutionContext['startDateTime']}") LocalDateTime startDateTime) { log.info("{} read {}", Thread.currentThread().getName(), startDateTime.format(DateTimeFormatter.ofPattern("yyyyMMddHH"))); RedisItemReader<String, TestLog> itemReader = new RedisItemReaderBuilder<String, TestLog>() .redisTemplate(redisTemplate()) .scanOptions(ScanOptions.scanOptions() .match("logs:" + startDateTime.format(DateTimeFormatter.ofPattern("yyyyMMddHH")) + ":*") .count(10000) .build()) .build(); return new SynchronizedItemReader<>(itemReader); }@Bean public TaskExecutor workerTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(5); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(10); executor.setThreadNamePrefix("Worker-Thread-"); executor.setAllowCoreThreadTimeOut(true); executor.setKeepAliveSeconds(30); return executor; }이렇게 했는데2025-12-17T13:34:07.885+09:00 ERROR 1568 --- [ main] o.s.batch.core.step.AbstractStep : Encountered an error executing step workerStep in job testLogJob java.lang.NullPointerException: Cannot invoke "org.springframework.data.redis.core.Cursor.hasNext()" because "this.cursor" is null at org.springframework.batch.item.redis.RedisItemReader.read(RedisItemReader.java:62) ~[spring-batch-infrastructure-5.2.4.jar:5.2.4] 이런 오류가 나.그리고 ItemReader쪽에log.info("{} read {}", Thread.currentThread().getName(), startDateTime.format(DateTimeFormatter.ofPattern("yyyyMMddHH")));로그를 찍었는데Worker-Thread-5 read 2025121712Worker-Thread-1 read 2025121706Worker-Thread-2 read 2025121700Worker-Thread-3 read 2025121718이런식으로 찍히고 있어.하나의 파티션을 여러 스레드가 읽어야 하는데 workerTaskExecutor가 서로 다른 파티션을 읽고 있어.내가 뭐 놓친게 있는걸까?
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
형 코드에 public static class 이게 뭐야 ?
형 코드 보고 있는데 아래와 같이 public static class로 되어 있어. 이게 무슨 의미야 ? 따로 static 메소드는 없는거 같은데
-
해결됨초심자를 위한 친절한 Java 한바퀴
소스코드 다운받을 수 있나요?
완강했습니다! 좋은 강의 감사합니다.소스코드 혹시 깃헙 등에서 제공이 가능한지궁금합니다.
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
표현 계층에서의 접근 지점이 다양해지는것과 이를 해결하기 위한 파사드의 도입에 대해 제미니님의 생각이 궁금합니다.
안녕하세요 제미니님, 유튜브부터 계속 꾸준히 보다가 강의 릴리즈 하신 후 바로 구매하여 듣고 있는 사람입니다. 먼저, 생각할 거리를 많이 주는 좋은 강의 감사드립니다. 다름이 아니라, 일단은 ProductSerivce 안에서 Product라는 개념만을 명확히 다루고자 하였기에 Controller에 타 개념의 Service들도 혼재되는? 그런 상황이라고 이해했습니다. 물론 다 선택의 영역이겠지만, 개발자분들 중에선 Presentation 영역(Controller)을 최대한 가볍게 가지고 가시려는 분들도 많은것 같습니다.그래서 대신에 여러 서비스들이 혼재되는 상황을 Facade 등을 도입해서 여러 개념의 서비스들에 대한 presentation 계층에서의 접근을 일원화하고 각 서비스단에서의 복잡성은 해결하고자 하는 케이스도 실무에서 종종 접했는데요. 이에 대해서는 제미니님께서 어떻게 생각하시는지 궁금합니다.
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
제품상세 코드 느끼기
안녕하세요 제미니님 유투브때부터 잘 보고 있습니다! 그 25분쯤에 controller에 여러 service들을 주입받아서 사용하시는 부분에 대한 질문이 있습니다.1. 그럼 격벽으로 넘어도 되는 개념 사이에서는 a service에서 b finder or b appender 를 호출해도 괜찮은건지..?2. 격벽으로 넘어서 안돼는 개념 사이에서는 강의에 나온것처럼 여러 service를 controller에서 받아서 response를 만들어야 하는건지.....?또 그렇게 되면 controller에서 여러 service를 알아야하니까 그건 또 문제가 없는지.. 궁금합니다!!제미니님은 주로 어떻게 하시나요!?감사합니다
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
격벽의 순환 참조(?)
안녕하세요. 지난번에도 질문 남겼었는데 또 찾아뵙게 되었습니다. user 의 경우 많은 개념들이 참조하게 될 것 같습니다.회원탈퇴라는 기능이 제공될 때, 회원이 탈퇴되면 관련된 개념들을 삭제해야된다고 할 경우 이를 어떻게 해결하는 것이 좋을까요?user 쪽에서 관련 개념들을 찾아서 삭제하기에는 개념 격벽간의 순환 참조(?)가 발생하게 될 것 같습니다.카프카나 메시지 큐 등을 이용해서 처리할 수 있을 것 같은데 현재 이를 처리할 수 있는 별도의 인프라는 없다고 가정해보고 싶습니다.그렇다면 어플리케이션 이벤트(ApplicationEventPublisher)를 사용하는 것이 방법이 생각납니다.하지만 이벤트를 사용하게 되면 어플리케이션의 로직 흐름을 보기가 조금 어려우지는 것 같다는 생각도 들어서 괜찮은 방법인지 고민이 됩니다.
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
ExitStatus
킬구형사용자 정의 ExitStatus를 애플리케이션 종료 코드로 활용하기. 이거 커스텀 ExitCodeGenerator까지 만들었으면 jar를 실행하고 나서 $LASTEXITCODE로 조회했을 때 코드 값이 바뀌어 있어야 하는거지?
-
미해결[코드팩토리] [초급] NestJS REST API 백엔드 완전 정복 마스터 클래스 - NestJS Core
where 키워드가 들어가는 메서드와 아닌 메서드
예를들어서 count는 조건을 걸때 where : {} 이런식으로 하는데 sum은 바로 {}만 하더라구요. 이런식으로 where 키워드를 쓰는 메서드와 안쓰는 메서드가 많이 나뉘던데 어떤 기준인지 알 수 있을까요? 헷갈리네요..
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
Batch6: jobOperator.startNextInstance() throws UnexpectedJobExecutionException
KILL-9형 도와줘,,!!!spring boot 4.0.0, spring batch6, java24 사용중이야 아래 코드를 스케줄러를 통해 "deleteSuspendedJob"을 1분마다 동작하게 하고 싶었어. 그리고 실제로 동작하긴 해. 딱 1번만.... @Configuration class DeleteSuspendedScheduler( private val jdbcTemplate: JdbcTemplate, private val jobOperator: JobOperator, private val jobRepository: JobRepository, private val transactionManager: PlatformTransactionManager, ) { @Scheduled(cron = "0 */1 * * * *") //1분마다 실행되길 기대함 fun runDeleteSuspendedJob() { jobOperator.startNextInstance(deleteSuspendedJob()) } @Bean fun deleteSuspendedJob(): Job = JobBuilder("deleteSuspendedJob", jobRepository) .incrementer(RunIdIncrementer()) .start( deleteSuspendedStep()) .build() @Bean fun deleteSuspendedStep(): Step = StepBuilder("deleteSuspendedStep", jobRepository) .tasklet(DeleteSuspendedTasklet(jdbcTemplate), transactionManager) .build() }아래는 에러 로그야. 2025-12-11T19:37:31.332+09:00 INFO 39640 --- [ main] com.clip.BatchApplicationKt : Started BatchApplicationKt in 6.378 seconds (process running for 6.894) 2025-12-11T19:38:00.017+09:00 INFO 39640 --- [ scheduling-1] o.s.b.c.l.s.TaskExecutorJobOperator : Launching next instance of job: [deleteSuspendedJob] with parameters: [{JobParameter{name='run.id', value=1, type=class java.lang.Long, identifying=true}}] 2025-12-11T19:38:00.019+09:00 INFO 39640 --- [ scheduling-1] o.s.b.c.l.s.TaskExecutorJobLauncher : Job: [SimpleJob: [name=deleteSuspendedJob]] launched with the following parameters: [{JobParameter{name='run.id', value=1, type=class java.lang.Long, identifying=true}}] 2025-12-11T19:38:00.052+09:00 INFO 39640 --- [ scheduling-1] o.s.batch.core.job.SimpleStepHandler : Executing step: [deleteSuspendedStep] 2025-12-11T19:38:00.065+09:00 INFO 39640 --- [ scheduling-1] c.c.b.b.t.DeleteExpiredBlacklistTasklet : 0개의 기간 만료된 탈퇴 이력(재가입 방지용) 레코드가 삭제되었습니다. 2025-12-11T19:38:00.067+09:00 INFO 39640 --- [ scheduling-1] o.s.batch.core.step.AbstractStep : Step: [deleteSuspendedStep] executed in 14ms 2025-12-11T19:38:00.067+09:00 INFO 39640 --- [ scheduling-1] o.s.b.c.l.s.TaskExecutorJobLauncher : Job: [SimpleJob: [name=deleteSuspendedJob]] completed with the following parameters: [{JobParameter{name='run.id', value=1, type=class java.lang.Long, identifying=true}}] and the following status: [COMPLETED] in 15ms 2025-12-11T19:39:00.007+09:00 INFO 39640 --- [ scheduling-1] o.s.b.c.l.s.TaskExecutorJobOperator : Launching next instance of job: [deleteSuspendedJob] with parameters: [{JobParameter{name='run.id', value=2, type=class java.lang.Long, identifying=true}}] 2025-12-11T19:39:00.009+09:00 ERROR 39640 --- [ scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task org.springframework.batch.core.job.UnexpectedJobExecutionException: Illegal state (only happens on a race condition): job instance already complete with name=deleteSuspendedJob and parameters={JobParameter{name='run.id', value=2, type=class java.lang.Long, identifying=true}} at org.springframework.batch.core.launch.support.SimpleJobOperator.startNextInstance(SimpleJobOperator.java:314) ~[spring-batch-core-6.0.0.jar:6.0.0] at org.springframework.batch.core.launch.support.TaskExecutorJobOperator.startNextInstance(TaskExecutorJobOperator.java:133) ~[spring-batch-core-6.0.0.jar:6.0.0] at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[na:na] at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-7.0.1.jar:7.0.1] at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-7.0.1.jar:7.0.1] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:158) ~[spring-aop-7.0.1.jar:7.0.1] at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:370) ~[spring-tx-7.0.1.jar:7.0.1] Caused by: org.springframework.batch.core.launch.JobInstanceAlreadyCompleteException: A job instance already exists and is complete for identifying parameters={JobParameter{name='run.id', value=1, type=class java.lang.Long, identifying=true}}. If you want to run this job again, change the parameters. at org.springframework.batch.core.launch.support.TaskExecutorJobLauncher.createJobExecution(TaskExecutorJobLauncher.java:149) ~[spring-batch-core-6.0.0.jar:6.0.0] at org.springframework.batch.core.launch.support.TaskExecutorJobLauncher.run(TaskExecutorJobLauncher.java:108) ~[spring-batch-core-6.0.0.jar:6.0.0] at org.springframework.batch.core.launch.support.SimpleJobOperator.startNextInstance(SimpleJobOperator.java:294) ~[spring-batch-core-6.0.0.jar:6.0.0]원래 Kill9형 강의 보고 잡 런쳐로 정상동작 하도록 만들었던 걸, 이번에 배치6로 올리면서 JobLauncher가 JobOperator로 옮겨졌다는 문서를 보고 바꾼뒤로 퇴근을 못하고 있어,,역시 공식 문서보단 kill9 형 문서를 보고 했어야 했던걸까??오퍼레이터와 스케줄러를 통해 잡을 특정 주기마다 동작하는 방법(위 내 코드)이 뭐가 잘못된건지 알려주면 고맙겠어!!형 제발 도와줘!!!cf.https://github.com/spring-projects/spring-batch/issues/5115
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
결제 관련 서킷 브레이커 전략, 데이터 정합성 및 타임아웃 설정 질문
안녕하세요, 제미니님!강의 완독 후, 결제 시스템의 안정성을 높이기 위해 개인 프로젝트에 외부 PG사 연동 구간에 장애 격리 처리를 직접 구현하며 경험하고 있습니다. 구현 과정에서 고민했던 설계 내용과 제 나름의 가설이 맞는지, 그리고 기존 강의 코드의 설정 의도에 대해 여쭤보고 싶습니다. 🙇🏻♂️ 1. 서킷 브레이커 도입 및 트랜잭션 분리 전략 @Component class BeeceptorPaymentClient( private val restClient: RestClient, private val circuitBreaker: CircuitBreaker, ) : PaymentClient { private val log = LoggerFactory.getLogger(javaClass) override fun requestPayment(command: PaymentCommand): PaymentResult { return circuitBreaker.run("beeceptor-payment") { executePayment(command) } .fallbackIfOpen { log.warn("[Circuit Open] Beeceptor 결제 서비스 차단됨. 잠시 후 재시도 필요.") throw CoreException(ErrorType.PAYMENT_EXTERNAL_API_UNAVAILABLE) } .getOrElse { e -> when (e) { is HttpClientErrorException -> { log.warn("[PAYMENT_REJECTED] 결제 거절: ${e.responseBodyAsString}") throw CoreException(ErrorType.PAYMENT_REJECTED) } is CoreException -> throw e else -> { log.error("[PAYMENT_FAILED] Beeceptor 결제 호출 실패", e) throw CoreException(ErrorType.PAYMENT_EXTERNAL_API_FAIL) } } } } }외부 PG사 장애 전파를 막기 위해 Resilience4j를 도입했고, 테스트를 위해 Beeceptor를 활용했습니다.설정 전략:COUNT_BASED (최소 10회, 실패율 50% Open), 단순 비즈니스 에러(4xx)는 ignoreExceptions로 제외하여 시스템 장애만 감지하도록 설정했습니다. 트랜잭션 분리(Facade 패턴): 트랜잭션에 대한 DB 커넥션 점유 시간을 최소화하기 위해 아래와 같이 로직을 3단계로 분리했습니다. 1. 검증: DB 조회 (ReadOnly 트랜잭션)2. 외부 요청: 외부 PG API 호출 (No 트랜잭션 + 서킷 브레이커 적용)3. 상태 업데이트: 결제 결과 저장 (Write 트랜잭션)@Component class PaymentConfirmFacade( private val paymentService: PaymentService, private val paymentClient: PaymentClient, ) { fun success(orderKey: String, externalPaymentKey: String, amount: BigDecimal): Long { // 1. [DB Transaction] 결제 검증 val command = paymentService.validatePayment(orderKey, externalPaymentKey, amount) // 2. [No Transaction] 외부 API 호출 val paymentResult = paymentClient.requestPayment(command) // 3. [DB Transaction] 결제 완료 처리 // 외부 API에서 받은 결과(transactionId 등)를 넘겨줌 return paymentService.completePayment(orderKey, paymentResult) } } [질문] Facade 패턴을 적용함에 따라 '외부 요청'(2번)은 성공했으나, '상태 업데이트 및 저장'(3번)에서 DB 장애 등으로 실패할 경우 데이터 불일치가 발생합니다. 현재 로직상 completePayment가 실패하면 트랜잭션이 롤백되어 TransactionHistory조차 남지 않고 Payment 상태는 READY로 유지됩니다.따라서 저는 배치/스케줄러를 통해 일정 시간이 지나도 READY 상태로 남아있는 결제 건들을 조회한 뒤, PG사 결제 내역 조회 API와 크로스 체크하여 누락된 결제를 보정하는 로직이 필요하다고 판단됩니다. 이러한 접근 방식이 실무에서사용하는 일반적인 보정 패턴인지 궁금합니다. 2. DB Connection/Socket Timeout 설정 의도와 Facade 적용 시의 상관관계외부 API 타임아웃(Connect 3s, Read 30s)을 학습하며 적용하던 중, 기존 강의 프로젝트의 db-core.yml 설정이 눈에 들어왔습니다.connection-timeout: 1100 (1.1초)socketTimeout: 3000 (3초)[질문 1] 보통 DB 타임아웃을 넉넉하게 잡는 경우도 있는데, 이렇게 타이트하게 설정하신 의도가 "트래픽 급증 시 DB 커넥션을 얻지 못하면 빠르게 실패 처리하여 스레드 풀 고갈을 막고 시스템 전체 장애를 방지하기 위함" 인지, 혹은 다른 운영 노하우가 담겨 있는 것인지 궁금합니다.[질문 2] 저는 Facade 패턴을 적용하여 외부 API 호출 시점에는 트랜잭션(DB Connection)을 점유하지 않도록 분리했습니다. 따라서 기술적으로는 DB 타임아웃과 서킷 브레이커(외부) 타임아웃을 서로 독립적인 관점으로 설정해도 된다고 판단했습니다.다만, 실무 운영 관점에서는 결국 '전체 사용자 대기 시간(User Latency)' 이라는 제약이 존재할 텐데, 이때 전체 응답 시간 제한 내에서 DB와 외부 API 타임아웃 비중을 어떻게 배분하시는지 강사님만의 기준이나 노하우가 궁금합니다 ! 긴 글 읽어주셔서 감사합니다.
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
비회원 개념 추가 시 개선 방향
요즘 쇼핑몰 커머스등은 비회원 주문이 있는 경우가 거의 대부분인 것 같은데 비회원 주문,결제의 개념이 추가된다면 어떻게 개선될 수 있을까요? api가 복제되는 느낌으로 가야하는지.. 주문, 장바구니, 결제 등등 유저아이디 대신 게스트아이디로 조회하는 등과 같이 작은 부분만 바뀌고 나머지 로직은 동일 반복될 것 같습니다.주문조회시에도 회원 비회원.. 서비스는하나를 쓰고 OrderFinder에서 함수로 나누는 방식도 생각납니다.어떤 방식이 더 있을까요?
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
jdbc 커서, 페이징에서 일대다 관계 데이터 뻥튀기 조회 처리 방법 질문
강의 섹션4. 데이터베이스를 지배하라. 챕터에서 아래는 JPA 방식으로 일대다 관계를 가진 데이터를 페치 조인으로 가져오는 예제 코드야.returnnewJpaCursorItemReaderBuilder<Post>().name("postBlockReader").entityManagerFactory(entityManagerFactory).queryString(""" SELECT p FROM Post p JOIN FETCH p.reports r WHERE r.reportedAt >= :startDateTime AND r.reportedAt < :endDateTime """).parameterValues(Map.of( "startDateTime", startDateTime, "endDateTime", endDateTime )) .build();근데 jdbc 커서 예제에서는 킬구형이 아래처럼 단일 테이블만 조회하는 예제를 사용했어.@Bean publicJdbcCursorItemReader<Victim> terminatedVictimReader() {returnnew JdbcCursorItemReaderBuilder<Victim>().name("terminatedVictimReader").dataSource(dataSource).sql("SELECT * FROM victims WHERE status = ? AND terminated_at <= ?") .queryArguments(List.of("TERMINATED", LocalDateTime.now())) .beanRowMapper(Victim.class) .build();}근데 내가 지금 하려고 하는 건 jdbc 커서 방식에서 일대다 관계 테이블 데이터를 조회해서 일일정산 데이터 만드는 기능으로 복습해보려고 하는데, 데이터가 뻥튀기 되서 강의 예제에서 해당 케이스를 찾아보려고 하는데, 못 찾아서 질문글 작성했어. jdbc 방식으로 일대다 관계 데이터를 Reader로 읽어와서 위에 jpa 구조로 매핑하려면 어떻게 해야하는지 알려줄 수 있어?@Bean @StepScope public JdbcCursorItemReader<OrderBatchJoinDto> jdbcCursorReader( @Value("#{jobParameters['orderDate']}") LocalDate orderDate) { LocalDateTime startOrderDate = orderDate.atStartOfDay(); LocalDateTime endOrderDate = orderDate.atTime(23, 59, 59); return new JdbcCursorItemReaderBuilder<OrderBatchJoinDto>() .name("jdbcCursorReader") .dataSource(dataSource) .sql(""" SELECT ob.ORDERS_BATCH_ID, ob.USER_ID, ob.STATUS, ob.ORDER_DATE_TIME, oib.ORDERS_ITEM_BATCH_ID, oib.ORDERS_BATCH_ID, oib.PRODUCT_BATCH_ID, oib.PRODUCT_NAME, oib.PRICE, oib.QUANTITY FROM orders_batch ob LEFT JOIN orders_item_batch oib ON ob.ORDERS_BATCH_ID = oib.ORDERS_BATCH_ID WHERE ob.ORDER_DATE_TIME BETWEEN ? AND ? ORDER BY ob.ORDERS_BATCH_ID, oib.ORDERS_ITEM_BATCH_ID """) .queryArguments(List.of(startOrderDate, endOrderDate)) .beanRowMapper(OrderBatchJoinDto.class) }public class OrderItemBatchDto { private Long id; private Long ordersBatchId; private Long productBatchId; private String productName; private int price; private int quantity; }public class OrderItemBatchDto { private Long id; private Long ordersBatchId; private Long productBatchId; private String productName; private int price; private int quantity; }
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
DB 레이어 잘 다루는 법
안녕하세요. DB 레이어를 다루는 것에 대해 몇가지 질문이 있습니다.프로젝트 버전 1.1 포인트 적립 트랜잭션리뷰 작성ReviewService.addReview)에서 포인트 적립PointHandler.earn )이 한 트랜잭션에서 이루어져야 될 것 같다고 생각했는데 분리되어 있습니다. 이렇게 처리해도 충분한지 여쭤보고 싶습니다.CancelService, PaymentService 에서는 같은 트랜잭션에 있음 낙관적 락 예외 처리PointBalanceEntity 에는 동시성 처리를 위해 @Version(낙관적 락)을 두었다고 해주셨습니다.현재는 낙관적 락 예외가 발생하면 500 에러가 발생할 것으로 보이는데, 저는 평소 최대한 이 낙관적 락 예외를 잡아서 적절히 다른 예외로 변환하여 던지도록 했었습니다.낙관적 락 예외에 대해서 500에러가 발생하도록 처리하는 것으로 충분할지 고민됩니다. Repository, JpaRepository 분리 현재 프로젝트는 서비스 -> 컴포넌트(Finder, Handler, ...) -> JpaRepository 형태로 계층 관계가 있는 것 같습니다.그런데 재미니님의 유튜브를 보면 서비스 -> 컴포넌트(Finder, Handler, ...) -> Repository(개념 객체를 다루는?) -> JpaRepository 형태의 계층을 이루고 있는 것으로 보입니다.첫번째 계층구조를 보면 종종 서비스에서 단순히 컴포넌트를 한번 호출하는 정도인 경우가 많은 것 같습니다. 두번째 계층구조를 사용한다면 서비스에서 단순히 컴포넌트를 한번 호출하고, 그 컴포넌트에서도 단순히 Repository를 한번 호출하는 정도인 경우도 많이 생길 것 같습니다.이러한 이유로 저는 첫번째 계층구조 정도로 충분한가? 라고도 생각했는데 두번째 계층구조를 선택하시게 되는 이유가 궁금합니다. 두번째 계층구조 관련해서 여러 영상들이 있겠지만 우선 저는 아래 영상 참고했습니다.https://www.youtube.com/watch?v=b5xWS8MYl0Q Repository 가 분리된 경우에서의 검증, 업데이트 로직데이터를 검증, 업데이트할 때 어떤 재미니님과 같은 프로젝트 구조를 가져간다면 어떤 식으로 코드가 나오게 될 지 궁금합니다.검증, 업데이트라 하면 제가 생각한 예시 상황은 리뷰가 7주일이 지나면 업데이트 할 수 없다는 요건이 있다고 가정해보겠습니다.제가 생각한 것은 아래와 같습니다.public class ReviewService { private final ReviewFinder reviewFinder; private final ReviewProcessor reviewProcessor; public void update(ReviewUpdate reviewUpdate) { var review reviewFinder.get(reviewUpdate.id()); if (review.canUpdate()) { throw new CoreException(); } var updatedReview = review.update(reviewUpdate); reviewProcessor.update(updatedReview); } }이전에 프로젝트 구조에 대해서 고민이 많이 되었을 때 재미니님의 유튜브를 보면서 프로젝트 구조를 잡는데 도움이 많이 되었습니다.그런데 제가 그 의도나 스타일을 완전히 이해하지는 못하고 있는 것 같아서 재미니님의 스타일들을 한번 배워보고 싶습니다.유튜브에서부터 정말 도움 많이 받고있습니다. 좋은 강의 감사합니다!!
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
SkipPolicy는 여러번 불릴 수 있는가?
skip policy 에 대한 질문Firebase message를 writer 쪽에서 사용하고,override fun shouldSkip(throwable: Throwable, skipCount: Long): Boolean { if (throwable !is BatchUnregisteredException) return true if (throwable.errorCode == FCM_UNREGISTERED_TOKEN || throwable.errorCode == FCM_MULTIPLE_TOKEN_ERROR) { throwable.tokens.forEach { fcmToken -> checkUnregisterToken(fcmToken) } } return true }skipPolicy에서 위와 같이 unregister token들을 제거해주려고 했어. 그리고, 테스트코드에서 제거 로직이 한번만 불렸는지 체크했는데, 총 3번이 불렸다고 테스트가 실패하더라구(실제 데이터는 1개라는 가정하에)GPT는 여러번 불릴 수 있다고, SkipListener 에서 onSkipWrite 에서 unregister 된 토큰을 제거하라고 하는데1. 실제로 skipPolicy는 여러번 불리는게 맞는지1-1. Skip 여부 체크1-2 Skip 처리 중에서도 체크1-3 Chunk 완료 처리 시에도 확인 이라는데 맞아 ,,?2. 보통 이러한 토큰 제거 작업이 있다면 어디서 수행하는게 맞는지 알려줘 ~
-
해결됨[코드팩토리] [초급] NestJS REST API 백엔드 완전 정복 마스터 클래스 - NestJS Core
BearerTokenGuard에서 db를 조회해서 유저 정보를 불러오는 이유?
BearerTokenGuard에서 db를 조회해서 유저 정보를 불러오는 이유가 궁금합니다.제가 스프링으로 개발했을 때는 role을 jwt에 담고 별도의 db 조회 없이 스프링 시큐리티에서 검증을 했어서요. 유저 엔티티가 필요하다면 서비스 레이어에서 조회를 하구요.NestJs 현업에서 쓰이는 일반적인 패턴이 궁금합니다
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
형 실무에서 배치 시스템은 어떤 식으로 HA를 구성해??
형! 퇴근도 못하고 일하다가 이제야 형 강의보면서 주말을 맞이하고 있어! 스프링 배치를 써서 분 단위, 하루 단위 KPI를 산출하는 배치 프로그램을 만드려고 하는데, 실무에서는 어떻게 HA를 구성하는 지 궁금해졌어. 가령, k8s에서 같은 배치를 돌리는 pod가 여러 개이면 배치가 동시에 돌 것 같고, pod가 한 개이면 하나의 배치 시스템이라서 위험할 것 같은데, 어떤 식으로 실무에서 하는 지 궁금해!