묻고 답해요
169만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결죽음의 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 의존성 추가 필요
-
해결됨The 10x AI-Native Developer: 회사에서 AI로 압도적 성과를 내는 법
업데이트 관련 문의입니다.
안녕하세요.26/1/17 1-3 claude memory 기능 변경 업데이트위의 업데이트 되는 내용들은 어디서 확인할 수 있을까요?
-
해결됨서버개발자 과제전형 완벽가이드 - 1편
NaverBookRepository.class 의 위치에 따른 모듈간의 의존성에 대해 질문드립니다.
학습관련 질문을 남겨주세요! 상세히 작성주시면 더 좋아요 🙂 안녕하세요, 강사님 🙂이번 질문도 혼자 먼저 구현 후, 강사님의 강의를 들으며 리팩토링을 진행하며 궁금증이 하나 생겨 질문 드립니다. 저는 search-api 모듈의 com.library.repository의 NaverBookRepository.class 위치를 강사님과 다르게 external:naver-client 에 위치시켰습니다.(NaverBook/KakaoBook은 external 에 종속된 구현체라고 생각) 그러다 보니, naver-client 모듈이 search-api 모듈에 있는 BookRepository, PageResponse, SearchResponse 를 알아야 했으며, naver-client 모듈이 search-api 모듈을 implementation('...') 으로 의존?참조? 하게 되었습니다. // search-api 모듈 public interface BookRepository { PageResponse<SearchResponse> search(...); } 이렇게 external -> search-api 로 흘러가는 의존 방향에 이질감이 듭니다.(외부 API 가 app 을 안다..?) 그래서 모듈간 의존 방향이 search-api -> external 되도록 강사님 강의처럼 NaverBookRepository 를 search-api 모듈에 위치 시켜도 NaverClient 때문에 search-api에 feign 종속성이 생긴 다는 것이 모듈간 의존성 격리가 되지 않아서 멀티모듈의 장점이 퇴색되었다고 생각합니다. 강사님은 과제가 아닌 실무에서 search-api 와 external 간의 모듈 격리를 어떻게 하시며, 의존 방향을 어떻게 하시는지 궁금합니다. 저의 부족한 지식을 확장할 수 있게 강사님의 인사이트 공유해주시면 감사하겠습니다!
-
미해결실전! 스프링부트 상품-주문 API 개발로 알아보는 TDD
POJO에서 Spring Test로 넘어갈 때 누락(해결됨)
안녕하세요 강의 정말 잘 듣고 있습니다현재 코틀린으로 직접 타이핑해보며 따라하고 있는데, 자바 공부에 대한 필요성을 느끼고 있습니다다름이 아니라 섹션 2의 POJO에서 스프링 테스트로 전환하며, 기존 테스트 디렉토리의 ProductServiceTest에 대부분 코드가 몰려 있던 구조에서 main 디렉토리에 파일이 많이 늘어서 구조를 맞추는 데 좀 어려웠습니다 중간에 누락된 부분이 있을까요? 만약 같은 분이 계시다면, 클래스명 선택한 다름(드래그로 하이라이트) f5가 이동, f6이 복사로 해당 클래스를 메인에 옮길 수 있습니다(윈도우/리눅스 인텔리제이 기준)확인했습니다! POJO에서 이미 제가 말씀드린 방법으로 리팩토링 수행하셨네요혹시나 헷갈리시는 분들 계실까봐 남겨두겠습니다
-
해결됨제미니의 개발실무 - 커머스 백엔드 레거시와 AI 활용편
ProductOption을 통한 FindProductOption에 관한 질문
ProductOption을 fetch해 올때 DB레벨에서 where이랑 orderby를 하지 않고 애플리케이션 레벨에서 코틀린 filter와 sort를 하신 이유가 따로 있을까요? 성능상 DB에서 전체적으로 데이터를 가져오고 인메모리 작업으로 filter와 sort를 하는게 불리하지 않나 싶어서 질문을 남깁니다.
-
해결됨제미니의 개발실무 - 커머스 백엔드 레거시와 AI 활용편
상수에 관련해서 질문있습니다.
요구 사항이 자주 변경되는 것을 가정하는 프로젝트인 만큼 isUnique에 관련된 상수나 Recent day에 관한 상수를 properties로 빼서 yml에 관리하는 방식으로 구현해보았는데. 실무에서는 어떤 경우에 yml에서 관리하는 방식을 고려하고 어느 상황에 그냥 상수를 코드에 남기는지 궁금합니다.
-
미해결실전에서 바로 써먹는 Elasticsearch 입문 (검색 최적화편)
멀티 필드 실무 질문드립니다.
안녕하세요 강사님 멀티필드 강의를보면 실무에서 검색조건을 줄때 정말 좋은 기능같은데 실무에서도 자주 쓰이는지 궁금합니다. 만약 거래데이터를 저장한다 했을때 필드가 한 20개정도 되는 상황인데 이때 상품명이나 구매자명 등에 멀티필드로 text 및 keyword를 준다면 고객 입장에서는 되게 좋겠지만 내부적으로 뭔가 필드가 더 생기는것같은데 실무에서도 저런식으로 저장공간?을 신경안쓰고 얼추 많이 쓰는지 궁금합니다.
-
미해결실전에서 바로 써먹는 Elasticsearch 입문 (검색 최적화편)
페이지네이션 질문드립니다.
안녕하세요 강사님페이지네이션 강의에서 1번글 ~ 7번글로 insert를 한 이후에 size를 지정하면 1번글부터 나오는데 id가 랜덤임에도 엘라스틱서치에는 mysql seq개념처럼 insert된것부터 document가 쌓여 그냥 조회하면 순차적으로 insert된것부터 나오게 되는걸까요?만약 아무런 조건없이 asc정렬을 하고싶다하면 그냥 _search만 해도 되는걸까요? ( 실무에서도 그냥 아무런 조건없이 순차적으로 보여준다하면 그냥 findAll만 하는지 궁금합니다 )
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
histories() 응답에 PointHistory.id를 포함한 이유가 궁금합니다/
안녕하세요.histories() 응답에 PointHistory.id가 포함되어 있던데, 이 값을 내려주신 의도가 궁금합니다.FE에서 이 id를 직접 활용해야 하는 지점이 있는지 궁금해서요.예를 들어 포인트 이력 상세 조회나 특정 이력을 식별해야 하는 기능을 염두에 두신 걸까요?현재로서는 다른 필드들만으로도 충분하지 않나 생각되어, id를 포함하신 이유가 궁금합니다!
-
해결됨서버개발자 과제전형 완벽가이드 - 1편
패키지 vs 모듈 선택 기준
학습관련 질문을 남겨주세요! 상세히 작성주시면 더 좋아요 🙂 안녕하세요, 재밌는 강의 만들어주셔서 감사합니다! 저는 먼저 구현 후, 강사님의 강의를 보며 리팩토링을 진행하고 있습니다. 제가 궁금한 것은 강사님은 external 모듈에서 naver/kakao 를 하위 모듈로 분리하셨는데, 저는 external 하위에 kakao 패키지, naver 패키지로 분리했었습니다. 그래서, 모듈로 분리하신 이유가 무엇인질 고민하다가 naver 와 kakao 의 수정되는 주기가 달라서 빌드/배포에 이점이 있겠다는 생각이 들었는데 강사님은 어떤 기준으로 모듈과 패키지를 선택하시는지 질문 드립니다.
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
오탈자 - @Transactional
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]안녕하세요. 영한님.프로젝트 환경설정 - JPA와 DB 설정, 동작확인 오탈자 제보합니다.강의에서는 Spring을 사용하고 있기 때문에Spring의 org.springframework.transaction.annotation.Transactional을 사용하셨지만수업자료는 jakarta.transaction.Transactional을 사용하고 있습니다.[1. 프로젝트 환경설정 | JPA와 DB 설정, 동작확인]import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional;import문 수정 내용입니다. 감사합니다.
-
미해결스프링부트를 이용한 웹 프로그래밍: 웹사이트 이렇게 만드는 거예요!
Dto와 Entity 사용 관련 질문
안녕하세요, 57강 관련 질문입니다. Dto 클래스를 완전히 없애버리고 Entity만 사용하는 건 불가능한 건가요? Entity가 DB 관련 어노테이션을 가지고 있다는 점을 제외하면 Dto 클래스와 굉장히 유사한 형태인데, 이 두 개를 왜 꼭 따로 정의하고 사용해야 하는 것인지 이해가 잘 안 갑니다!
-
미해결백엔드 개발을 위한 필수 강의 - 스프링 부트3
LiveReload++ 비활성화
'섹션 1. 1장 스프링 부트 개발 준비하기' - '1. JDK와 STS설치, 웹 서비스의 동작 원리, Spring Boot Devtools과 롬복 설치 등' 관련된 내용 입니다.'LiveReload++'가 '이 확장 프로그램은 Chrome 확장 프로그램 권장사항을 준수하지 않으므로 더 이상 지원되지 않습니다.'라고 뜨면서 '크롬에 추가' 버튼이 비활성화 되네요.혹시 LiveReload++와 비슷한 대체할만한 게 있나요?
-
해결됨Spring Boot, AWS로 백엔드 서비스 한 사이클 완성하기
EC2 실습 중 docker-compose / docker ps 실행 시 권한 오류 문의
안녕하세요, 강사님.실습 자료대로 진행 중인데 EC2에서 docker-compose up -d, docker ps -a 같은 명령을 실행할 때 permission denied while trying to connect to the Docker daemon socket 오류가 발생해서 문의드립니다.확인해보니 현재 제 계정(ec2-user)은 docker 그룹에 포함되어 있지 않고, /var/run/docker.sock는 root docker 권한으로 되어 있어서 sudo를 붙여야만 명령이 실행되고 있습니다.혹시 강사님 실습 환경에서는 이미 docker 그룹 설정이 되어 있어서 sudo 없이 진행이 가능했던 것인지,아니면 제가 중간에 빠뜨린 설정 단계가 있는지 궁금합니다.현재는 sudo docker-compose up -d, sudo docker ps -a처럼 sudo를 붙이면 진행은 되는데,실습 의도상 계속 sudo를 사용해도 괜찮은지 확인 부탁드립니다.감사합니다.
-
해결됨Springboot 모니터링 시스템 구축 (프로메테우스 + 그라파나)
actuator에서 health 외의 다른 URL은 404 Notfound 에러가 발생합니다.
Spring boot java를 사용하고있고, 현재 1. Spring Actuator 셋팅중입니다.actuator에서 health 외의 다른 URL은 404 Notfound 에러가 발생합니다.application.yml을 사용하여 include에서도 "*"로 전체허용 해놨는데도 안되네요
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
SettlementTargetRepository Jquery 질문
@Query( """ SELECT new io.dodn.commerce.storage.db.core.SettlementTargetSummary( settlement.merchantId, settlement.settlementDate, SUM(settlement.targetAmount), COUNT(settlement.id), COUNT(DISTINCT settlement.orderId) ) FROM SettlementTargetEntity settlement WHERE settlement.settlementDate = :settlementDate GROUP BY settlement.merchantId, settlement.settlementDate """, ) fun findSummary(settlementDate: LocalDate): List<SettlementTargetSummary>이부분에서 where절에서 이미 settlementDate를 필터링하고 있는데 group by에서 settlementDate가 필요한 이유가 따로 있을까요? 어차피 parameter로 넘어온 settlementDate만 조회가 되는 로직이라 Group By에서는 필요가 없어보여 질문 남깁니다.
-
미해결스프링부트를 이용한 웹 프로그래밍: 웹사이트 이렇게 만드는 거예요!
application.properties 작성 관련 질문
안녕하세요, 강의 잘 듣고 있습니다. '데이터베이스 연동하기 - I' 강의에서 application.properties 파일에spring.datasource.password=1234로 작성하셨는데, 실제로 프로젝트를 할 때에도 이렇게 비밀번호를 직접 노출하는 방식으로 작성을 하나요? 뭔가 비밀번호이다 보니 코드로 이렇게 노출시키면 안 될 것 같은 느낌이 들어서 궁금합니다!
-
미해결실전에서 바로 써먹는 Elasticsearch 입문 (검색 최적화편)
insert , upsert 질문드립니다.
안녕하세요, 도큐먼트 저장,조회,수정,삭제하기 영상을보고 궁금한게 있어 질문드립니다 .id를 직접 지정해서 저장하는경우PUT /users/_create/1{"name:"testPUT"}POST /users/_creat/1{"name:"testPOST"}이러면 결과가 PUT으로 할때나 POST로 할때나 결국 마지막꺼로 덮여씌워지는데 그냥 doc가 덮여져서 저장됐다 생각하면 될까요? 또 PUT이나 POST나 기능이 동일한데 일반 HTTP 메서드와 비슷하게 생각하면 되는걸까요?두번째 질문으로는 UPSERT의 경우나 UPDATE ( 일부필드만수정 ) 의 경우UPDATE로 age만 고치나 UPSERT로 그냥 전체 기존값을 쓰고 age만 바꿔서 하나 결과값은 동일한데 퍼포먼스차이나 실무에서는 그냥 update보다는 해당 Id에 다시 insert한다던가 어떠한 식으로 진행되는지 궁금합니다.
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
..
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. 지금 영상 따라 진행하던도중 막혔는데 최신 버전 설명도 읽어봤으나 무슨 말인지도 하나도 모르겠습니다.. 계속 오류만 뜨고 화면도 전혀 다르고...