묻고 답해요
169만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
형 책 어떻게 받아?
형 책 어떻게 받아?형 책 어떻게 받아?형 책 어떻게 받아?
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
JpaCursorItemReader의 정렬 조건 부재의 영향
형 질문있어.페이징 기반 ItemReader에서는 예제와 같이 ORDER BY를 추가해야 한다. ORDER BY가 없으면 매 페이지를 읽을 때마다 데이터의 순서가 보장되지 않아 일부 데이터가 누락되거나 중복될 수 있다.라고 했잖아.이 말은 곧 "JpaCursorItemReader 는 ORDER BY를 추가하지 않아도 괜찮다"로 들리는데 맞아?GPT는 아니라고 하거든.cursor 기반도 마찬가지로 ORDER BY가 없으면 재실행마다 DB에 정렬 순서를 위임하는데, DB는 쿼리 플랜이 변경되는 등 여러 원인들에 의해 실행마다 달라질 수 있대.뭐가 맞아?
-
해결됨Spring Batch 입문: 3시간 만에 끝내는 대용량 처리의 기초
소스코드 빈 파일 문의드려요
안녕하세요저도 아랫 분처럼 캐시를 삭제해도 소스코드가 압축해제도 안되고 안에가 비어있습니다.개인 이메일로 자료 보내주실 수 있을까요 ?
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
"컴파일 시점에 없는 값을 어떻게 참조할 것인가?" 섹션
step bean은 @jobscope, @stepscope을 붙이지 말라했는데 왜 이 예제에서는 붙인건지 모르겠어요
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
형 이번에 낸 책이랑 강의 내용에 차이가 있어?
교보 앱 보다가 형이 낸 책을 발견 했는데 이 강의랑 내용 차이가 있어서 읽는걸 추천하는지 궁금해책 출간한거 축하해
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
형 나 몰래 책내면 모를 줄 알고?
형 나 매일마다 교보 눈팅하는 데 형 책 나와서 깜짝 놀랐잖아! 축하해~ 책쓰는 거 엄청 힘든데 고생했어~ https://product.kyobobook.co.kr/detail/S000219973675
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
강의 중복 확인 요청
섹션 9부터 중복으로 내용이 있는데 확인 바란다
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
중복내용 제보?!
안녕 킬구형 강의는 구매한지 오래됐지만 지금에서야 공부하고 있어 별건 아닌데! 청크처리와 트랜잭션 부분이 2번 들어가있어서 알려줄려고! 킬구형 덕분에 싸게 구매해서 정말 공부 열심히 하고 있어!다음것도 기대할게!
-
해결됨카카오 면접관의 실무 밀착형 Spring Batch: 대용량 데이터 처리의 모든 것
여러 파드 환경에서 단일 실행 보장 방식
단일 실행이 보장되는 이유로 Db를 통해서 값을 가져오고 있으며 db에서 동시성을 방어해주고 있기 때문이라고 해주셨는데요.FindRunningJobExecutions 는 단순 select문이 아니고 내부적으로 비관적 락으로 동시 접근을 막아주는 구조인가요? 저는 여러 파드인 상황에서는 보여주신 코드가 동시성 이슈로 인해 주어진 잡이 한 번만 실행된다는 것을 보장하기 힘들 것 같다고 생각했습니다.이외에도 여러 파드인 상황이라면 실무에서 어떤 요소를 고려하는지 궁금합니다.학습 내용에선 currentimestamp를 잡 파라미터에 넣어서 매번 새로운 잡 인스턴스로 취급/실행하는 형태를 보여주셨는데, 이로인해 멀티파드 환경에서 특정 잡의 중복 실행 방지 혹은 특정 잡 파라미터 구성에서의 중복 실행 방지에 대한 요건 구현 시 영향도/고려 사항이 있는지 여부와 아니면 currentimestamp 잡 파라미터를 실무에서 빼기도 하는지 궁금합니다운영 시 중복 실행 문제 및 잡 재시도에 대해 고민하다 나온 질문입니다. 혹시 애초에 대부분의 배치 잡과 스탭 로직을 멱등하게 동작하도록 설계 및 코드 작성을 해야하는 걸까요?
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
Json 요청 처리
형 나 지금 너무 재밌어서 잘 따라하던중 별거 아닌거에서 막혀서 좀 힘들어형이 알려준 json 파라미터 넘기는 방법으로 해도 안되고 gpt 찾아서 진행 한거도 다 안돼는데 윈도우 환경에서 좀 힘든걸까 ?? 다른 수강생분이 올려준거도 봤는데 답변에 달아준 방법도 동작하지 않아 @Bean public JsonJobParametersConverter jobParametersConverter() { return new JsonJobParametersConverter(); } @Bean @StepScope public Tasklet terminatorTasklet( @Value("#{jobParameters['infiltrationTargets']}") String infiltrationTargets ) { return (contribution, chunkContext) -> { String[] targets = infiltrationTargets.split(","); log.info("⚡ 침투 작전 개시"); log.info("첫 번째 타겟: {} 침투 시작", targets[0]); log.info("마지막 타겟: {} 에서 집결", targets[1]); log.info("🎯 임무 전달 완료"); return RepeatStatus.FINISHED; }; }
-
해결됨카카오 면접관의 실무 밀착형 Spring Batch: 대용량 데이터 처리의 모든 것
job, step execution 관련 질문 드립니다.
안녕하세요.잡이 어떻게 스텝에서 사용하는 컨텍스트 값까지 가지고 있는지 잘 이해가 되지 않아 질문드립니다.분명 JobExecutionContext에 넣은 것이 아니라 StepExecutionContext에 값을 저장했는데, 확인해보니 JobExecutionContext에도 동일하게 저장된 것처럼 보여서 헷갈렸습니다.제가 이해한 바로는 JobExecutionContext와 StepExecutionContext는 서로 다른 영역이고,JobExecutionContext는 step 간 공유용, StepExecutionContext는 해당 step 전용으로 알고 있습니다.그런데 왜 StepExecutionContext에 넣은 값이 JobExecutionContext에도 같은 형태로 보이는지 잘 모르겠습니다.
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
[건의][6장][작전1] deprecated 메소드
형 ... @Bean public Step threatAnalysisStep( JpaPagingItemReader<Human> humanThreatDataReader, ItemProcessor<Human, TargetPriorityResult> threatAnalysisProcessor, FlatFileItemWriter<TargetPriorityResult> targetListWriter ) { return new StepBuilder("threatAnalysisStep") .<Human, TargetPriorityResult>chunk(10, transactionManager) .reader(humanThreatDataReader) .processor(threatAnalysisProcessor) .writer(targetListWriter) .taskExecutor(taskExecutor()) // deprecated 되어서 안씀 // .throttleLimit() .build(); } ... 에서 .throttleLimit() 부분이 5 이후로 deprecated 된다고 명시되어 있어. 관심사 분리로 인해서 taskExecutor 정의 부분에서 설정하라고 권장하는 것을 확인했거든. 현재 강의가 사실상 Spring Batch 5.x 까지의 기본 내용과 원리를 살펴봤잖아 ? 그렇다 하더라도 혼란을 줄 수 있기 때문에 해당 부분은 삭제하던지 아니면 추가 내용을 기재해야 할 것으로 보여.
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
[예제][3장][작전2] windows 에서 마지막 예제
... @Slf4j @Configuration @RequiredArgsConstructor public class LogProcessingJobConfig { private final JobRepository jobRepository; private final PlatformTransactionManager transactionManager; private final String BASE_PATH = "C:\\Users\\user\\Desktop\\batch\\section3\\flatfileitemwriter"; @Bean public Job logProcessingJob( Step createDirectoryStep, Step logCollectionStep, Step logProcessingStep ) { return new JobBuilder("logProcessingJob", jobRepository) .start(createDirectoryStep) .next(logCollectionStep) .next(logProcessingStep) .build(); } @Bean public Step createDirectoryStep(SystemCommandTasklet mkdirTasklet) { return new StepBuilder("createDirectoryStep", jobRepository) .tasklet(mkdirTasklet, transactionManager) .build(); } @Bean @StepScope public SystemCommandTasklet mkdirTasklet( @Value("#{jobParameters['date']}") String date ) { SystemCommandTasklet tasklet = new SystemCommandTasklet(); tasklet.setWorkingDirectory(BASE_PATH); String collectedLogsPath = "collected_ecommerce_logs\\" + date; String processedLogsPath = "processed_logs\\" + date; try { tasklet.setCommand("cmd.exe", "/c", "mkdir", collectedLogsPath, processedLogsPath); tasklet.setTimeout(3000); } catch (Exception e) { log.error("mkdirTasklet error: ", e); } return tasklet; } @Bean public Step logCollectionStep(SystemCommandTasklet scpTasklet) { return new StepBuilder("logCollectionStep", jobRepository) .tasklet(scpTasklet, transactionManager) .build(); } @Bean @StepScope public SystemCommandTasklet scpTasklet( @Value("#{jobParameters['date']}") String date ) { SystemCommandTasklet tasklet = new SystemCommandTasklet(); tasklet.setWorkingDirectory(BASE_PATH); String processedLogsPath = "collected_ecommerce_logs\\" + date; StringJoiner commandBuilder = new StringJoiner(" & "); for (String host : List.of("localhost")) { // Windows 환경에서 해당 코드를 적용하려면 ssh 관련 설정들을 찾아야 해서 주석 처리 // String command = String.format("scp %s:~/ecommerce_logs/%s.log %s\\%s.log", host, date, processedLogsPath, host); String sourcePath = BASE_PATH + "\\ecommerce_logs\\" + date + ".log"; String command = String.format("copy %s %s\\%s.log", sourcePath, processedLogsPath, host); commandBuilder.add(command); } tasklet.setCommand("cmd.exe", "/c", commandBuilder.toString()); tasklet.setTimeout(10000); return tasklet; } @Bean public Step logProcessingStep( MultiResourceItemReader<LogEntry> multiResourceItemReader, LogEntryProcessor logEntryProcessor, FlatFileItemWriter<ProcessedLogEntry> processedLogEntryJsonWriter ) { return new StepBuilder("logProcessingStep", jobRepository) .<LogEntry, ProcessedLogEntry>chunk(10, transactionManager) .reader(multiResourceItemReader) .processor(logEntryProcessor) .writer(processedLogEntryJsonWriter) .build(); } @Bean @StepScope public MultiResourceItemReader multiResourceItemReader( @Value("#{jobParameters['date']}") String date ) { MultiResourceItemReader<LogEntry> resourceItemReader = new MultiResourceItemReader<>(); resourceItemReader.setName("multiResourceItemReader"); resourceItemReader.setResources(getResources(date)); resourceItemReader.setDelegate(logFileReader()); return resourceItemReader; } private Resource[] getResources(String date) { try { String formattedBasePath = BASE_PATH.replace("\\", "/"); String location = "file:///" + formattedBasePath + "/collected_ecommerce_logs/" + date + "/*.log"; PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); return resolver.getResources(location); } catch (IOException e) { throw new RuntimeException("Failed to resolve log files", e); } } @Bean public FlatFileItemReader<LogEntry> logFileReader() { return new FlatFileItemReaderBuilder<LogEntry>() .name("logFileReader") .delimited() .delimiter(",") .names("dateTime", "level", "message") .targetType(LogEntry.class) .build(); } @Bean public LogEntryProcessor logEntryProcessor() { return new LogEntryProcessor(); } @Bean @StepScope public FlatFileItemWriter<ProcessedLogEntry> processedLogEntryFlatFileItemWriter( @Value("#{jobParameters['date']}") String date ) { String outputPath = Paths.get(BASE_PATH, "processed_logs", date, "processed_logs.jsonl").toString(); ObjectMapper objectMapper = new ObjectMapper(); JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"))); objectMapper.registerModule(javaTimeModule); return new FlatFileItemWriterBuilder<ProcessedLogEntry>() .name("processedLogEntryJsonWriter") .resource(new FileSystemResource(outputPath)) .lineAggregator(item -> { try { return objectMapper.writeValueAsString(item); } catch (JsonProcessingException e) { throw new RuntimeException("Error Converting item to JSON", e); } }) .build(); } @Data public static class LogEntry { private String dateTime; private String level; private String message; } @Data public static class ProcessedLogEntry { private LocalDateTime dateTime; private LogLevel level; private String message; private String errorCode; } public enum LogLevel { INFO, WARN, ERROR, DEBUG, UNKNOWN; public static LogLevel fromString(String level) { if (level == null || level.trim().isEmpty()) { return UNKNOWN; } try { return valueOf(level.toUpperCase()); } catch (IllegalArgumentException e) { return UNKNOWN; } } } public static class LogEntryProcessor implements ItemProcessor<LogEntry, ProcessedLogEntry> { private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_DATE_TIME; private static final Pattern ERROR_CODE_PATTERN = Pattern.compile("ERROR_CODE\\[(\\w+)]"); @Override public ProcessedLogEntry process(LogEntry item) throws Exception { ProcessedLogEntry processedLogEntry = new ProcessedLogEntry(); processedLogEntry.setDateTime(parseDateTime(item.getDateTime())); processedLogEntry.setLevel(parseLevel(item.getLevel())); processedLogEntry.setMessage(item.getMessage()); processedLogEntry.setErrorCode(extractErrorCode(item.getMessage())); return processedLogEntry; } private LocalDateTime parseDateTime(String dateTime) { return LocalDateTime.parse(dateTime, ISO_FORMATTER); } private LogLevel parseLevel(String level) { return LogLevel.fromString(level); } private String extractErrorCode(String message) { if (message == null) { return null; } Matcher matcher = ERROR_CODE_PATTERN.matcher(message); if (matcher.find()) { return matcher.group(1); } // ERROR 문자열이 포함되어 있지만 패턴이 일치하지 않는 경우 if (message.contains("ERROR")) { return "UNKNOWN_ERROR"; } return null; } } } 윈도우즈에서는 scp 명령어 관련해서 windows 서비스에서 open-ssh-server 실행현재 사용하고 있는 사용자(windows 사용자) 가 관리자 권한을 보유 할 경우 programdata/sshd_config 파일 수정 필요기타 파일 권한 정리 필요로 인하여 scp 프로그램을 windows 환경의 intellij idea 에서 id, 비번치게끔 ide 콘솔에서 지원하지 않는거 같아. 그래서 제미니가 추천해준대로 실습 환경에서 scp 접속 단을 copy 로 그냥 퉁치는 것으로 바꿔놨어. 나처럼 windows 환경인 사람들이 있으면 혹시나 참고했으면 좋겠네. ObjectMapper, JavaTimeModule 같은 경우는 org.springframework.boot:spring-boot-starter-json 의존성 추가 필요
-
해결됨Spring Batch 입문: 3시간 만에 끝내는 대용량 처리의 기초
소스코드 빈파일 문의
안녕하세요, 자료 다운로드를 통해 소스코드를 다운 받았는데 빈 압축파일로 나오는 상황입니다확인 한번만 부탁 드립니다감사합니다
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
[오타][3장][작전1] 형 이건 빨리 해줘
명령어에서 startDateTime, endDateTime 의 jobParameters 부재 코드에서 @StepScope 어노테이션과 startDateTime, endDateTime 의 jobParameters 파라미터 부재 1번은 다른 사람이 심지어 이전에 쓴 문제더라고 ? 아마 형이 반영 안한건 모아서 한번에 수정할 계획인거 같아 보이네.2번의 경우에는 @StepScope 도 빠져서 JobParameter 를 입력해도 오류가 발생했어. 또한 해당 메소드의 파라미터 부분을 1번과 마찬가지로 jobparameter로 받아야 할 것으로 보여져. 읽다가 하나씩 돌려보는데, 오류 터져서 놀라가지고 시간을 좀 썼어, 형. 1번은 내가 이해하는데, 2번은 형 실수가 좀 큰듯.
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
[오타] "리스너 활용: 시스템 완전 장악 매뉴얼" 부분
질문 가이드 읽고 삭제 후 오타제보 리스너 활용: 시스템 완전 장악 매뉴얼... 실행 결과에 따른 후속 처리: Job과 Step의 실행 상태를 리스너에서 직접 확인하고 그에 따른 조치를 수 있다.-> ~그에 따른 조치를 할 수 있다.-> 글자가 빠진듯?
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
[오타] "JobScope와 StepScope 사용 시 주의사항" 부분
질문 가이드 읽고 삭제 후 오타제보 JobScope와 StepScope 사용 시 주의사항... 2. Step 빈에는 @StepScope와 @JobScope와를 사용하지 말라.-> @Step 빈에는 @StepScope 와 @JobScope 와를 사용하지 말라-> 말이 확실히 이상해
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
spring boot 4 + spring batch 6 설정 변경?
형 스프링부트 4에서는 jdbc starter 추가 안하면 안되던데 뭐지```build.gradle.ktsdescription = "batch" dependencies { implementation("org.springframework.boot:spring-boot-starter-jdbc") implementation("org.springframework.boot:spring-boot-starter-batch") implementation("org.springframework.boot:spring-boot-h2console") runtimeOnly("com.h2database:h2") testImplementation("org.springframework.boot:spring-boot-starter-batch-test") }```implementation("org.springframework.boot:spring-boot-starter-jdbc")위 처럼 의존성 명시적으로 처리해주기 전에는 아래 에러떳었어.Could not autowire. No beans of 'PlatformTransactionManager' type found.
-
해결됨Spring Batch 입문: 3시간 만에 끝내는 대용량 처리의 기초
jobParameter를 통한 멱등성 질문
안녕하세요 우선 좋은 강의 감사드립니다. 덕분에 Batch에 대한 기본적인 틀을 잡을 수 있을 것 같아요! 중급강의도 기대됩니다.실습을 따라가던 와중, 멱등성을 유지하기 위해서 궁금한점이 생겨서 질문드립니다.같은 파라미터로 완료된 job은 재실행을 허용하지 않기 때문에 parameter로 time을 넣어주셨는데요.그렇게 되면 만약에 job을 실패지점부터 재실행 하고 싶은 경우에는 targetDate 뿐만 아니라 실패한 시간대에 time 파라미터까지 넣어서 재시작을 해야하는 것인가요?만약에 time 파라미터를 추가한 상태에서, 이전 실행과 다른 time 파라미터로 완료된 job을 재실행 할경우에는 정산 테이블에서 targetDate에 해당하는 값을 DELETE를 하고 INSERT를 진행해야 멱등성이 유지될 것 같습니다. 이럴 경우 DELETE 같은 경우는 Settlement INSERT 작업 이전에 별도의 Step으로 구성이 되야할까요?만약에 DELETE를 해야한다면 DELETE의 경우에는 targetDate의 해당 하는 값을 한번에 전부 지우고 INSERT를 시작하는지 궁급합니다..!
-
해결됨Spring Batch 입문: 3시간 만에 끝내는 대용량 처리의 기초
bootRun FAILED 에 대한 문의
강사님 안녕하세요~! 배치 실패시 서버 담당자에게 메일 보여주기 수업 잘들었습니다! 마지막에 실패시 리스너 동작을 테스트 하시기 위해서 예외를 던지신듯 합니다. 그런데 실습 결과가 강사님과 다르게 bootRun FAILED라고 나와서요. (> Task :bootRun FAILEDFAILURE: Build failed with an exception.) 사소한 것 같기는 한데 이유가 궁금해서 글남깁니다~ 좋은 하루되세요~~!