수업 챕터 14. 세션 로그인 ▶ ︎ 구현과 동작 에서 6분 35초쯤에 Docker 에서 Redis Container 로 진입하시고 /data # redis-cli 명령을 작성하실 때, 원래는 이런 명령어를 사용하시면 안된다고 하시는데 그럼 원래는 무슨 명령어를 사용하는지 궁금하네요
안녕하세요, 강사님. 강의와 직접적인 내용은 아니지만, 사내에서 발생한 Spring Batch 오류와 관련해 조언을 구하고자 문의드립니다. 혹시 시간이 괜찮으시다면 의견을 주시면 정말 감사하겠습니다. 1. 사용 환경 Java 1.8 Spring Boot 2.7.18 Spring Batch 4.3.10 2. 문제 상황 Spring Batch의 ExecutionContext 에 한글 문자열을 저장한 이후, 다음 배치 실행 시 아래와 같은 오류가 발생하며 Job이 지속적으로 실패하는 현상이 있었습니다. java.lang.IllegalArgumentException: Unable to deserialize the execution context at org.springframework.batch.core.repository.dao.JdbcExecutionContextDao$ExecutionContextRowMapper.mapRow ~ Caused by: com.fasterxml.jackson.core.JsonParseException: Invalid UTF-8 start byte 0xb2 해당 Job은 chunk 기반 배치이며, 마지막 close() 시점에 다음 Step에서 사용할 정보를 ExecutionContext 에 저장하고 있었습니다. 이때 ResultMsg: "정상 처리" 와 같이 한글 문자열 을 저장한 이후부터 문제가 발생했습니다. 3. 원인 분석 IDE(Eclipse) 인코딩: UTF-8 DB 인코딩: NLS_CHARACTERSET = KO16MSWIN949 BATCH_JOB_EXECUTION_CONTEXT 테이블에 데이터가 저장되는 구조로 확인했습니다. SHORT_CONTEXT (VARCHAR2(2500)) SERIALIZED_CONTEXT (CLOB) 2500byte 이하의 데이터는 SHORT_CONTEXT 에 저장되는데, 해당 컬럼이 MS949 기반 VARCHAR2 이다 보니 UTF-8 기반으로 직렬화된 ExecutionContext 내 한글 데이터가 깨지면서 다음 실행 시 역직렬화 실패가 발생한 것으로 판단했습니다. 4. 고민 중인 해결 방안 현재 아래와 같은 방안들을 검토 중입니다. ExecutionContext에 한글 자체를 사용하지 않도록 제한 로깅/메시지 성 데이터는 저장하지 않거나, 필요 시 영문만 사용 ExecutionContext에 putString 시 인코딩 설정 ExecutionContext 직렬화 방식 변경 ExecutionContextSerializer 를 DefaultExecutionContextSerializer 명시정으로 설정 (Spring Batch 4는 기본적으로 JacksonExecutionContextStringSerializer) 5. 질문 강사님께서 보시기에 위 상황에서 가장 권장되는 해결 방향 또는 실무적으로 안전한 접근 방식 은 무엇이라고 생각하시는지 궁금합니다. 긴 글 읽어주셔서 감사드리며, 가능하실 때 조언 주시면 많은 도움이 될 것 같습니다. 감사합니다.
자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
@PutMapping("/user") public void updateUser(@RequestBody UserUpdateRequest request){ String readSql = "select * from user where id=?"; String sql = "update user set name=? where id=?"; jdbcTemplate.update(sql, request.getName(), request.getId()); } 웹페이지에서 update를 발생시켜서 put 요청이 올라 왔을때 UserUpdateRequest dto에 id와 name이 들어 간다는건 웹에서 요청이 올라왔을때 json에 이미 해당 name에 대한 id 정보가 있다는 거죠? id는 db에서 자동적으로 생성해 준다고 했었는데, 이 id를 웹에서는 어떻게 알고 있나요?
안녕하세요 토비님!! 강의를 완강하고 제 프로젝트를 리팩토링하면서 피드백받고싶은점이 생겨 질문글을 올립니다. Repository Port를 기술에 종속시키지 않기 위해 Adapter에서 JPA, MyBatis, QueryDSL을 조합하는 구조를 선택했는데, 이 설계 방향이 적절한지 조언을 구하고 싶습니다. 현재 구조 core/ └── domain/ └── application/ └── required/ ← Port 인터페이스 └── ~Repository adapter/ └── persistence/ ├── ~RepositoryAdapter ← Port 구현체 ├── ~JpaRepository ← Spring Data JPA └── ~MybatisMapper ← Mybatis 매퍼 인터페이스 의존 관계 [Service] → [Repository] ←impl― [RepositoryAdapter] → [JpaRepository] → [MybatisMapper] → [JPAQueryFactory] (core) (core) (adapter) (adapter) 예시 @Repository @RequiredArgsConstructor public class ItemRepositoryAdapter implements ItemRepository { private final ItemJpaRepository itemJpaRepository; private final ItemMybatisMapper itemMybatisMapper; @Override public List<Item> findBySearchRequest(ItemSearchRequest request) { return itemMybatisMapper.findBySearchRequest(request) .stream() .map(Item::from) .toList(); } @Override public List<Item> saveAll(List<Item> items) { return itemJpaRepository.saveAll(items); } } @Repository @RequiredArgsConstructor public class AuctionRepositoryAdapter implements AuctionRepository { private final JPAQueryFactory jpaQueryFactory; private final AuctionJpaRepository auctionJpaRepository; @Override public void deleteAllByRegionAndRealmId(RegionType region, Long realmId) { QAuction qAuction = QAuction.auction; BooleanBuilder filter = new BooleanBuilder(); filter.and(qAuction.region.eq(region)); if (realmId == null) { filter.and(qAuction.realmId.isNull()); } else { filter.and(qAuction.realmId.eq(realmId)); } jpaQueryFactory.delete(qAuction) .where(filter) .execute(); } @Override public int saveAll(List<Auction> auctions) { if (auctions.isEmpty()) return 0; return auctionJpaRepository.saveAll(auctions).size(); } } 이 구조를 선택한 이유 동적 쿼리, 벌크 연산 등 JPA만으로 해결하기 어려운 케이스가 있어 MyBatis와 QueryDSL을 병행 사용하고 있습니다. 일반적인 방식인 Port 인터페이스가 Spring Data JPA Repository를 상속하는 구조를 채택하지 않은 이유: MyBatis나 QueryDSL 기반 구현체를 만들 수 없음 CustomRepository 인터페이스를 별도로 만들어야 하는 복잡도 증가 현재 방식의 장점: Port 인터페이스가 순수 Java 인터페이스로 유지됨 RepositoryAdapter에서 상황에 맞는 기술을 자유롭게 조합 가능 추가 인터페이스 없이 단순한 구조 유지 저는 현재 구조가 의존 관계도 외부에서 내부로 향하고 테스트도 쉬워서 괜찮다고 생각하는데 토비님의 생각도 듣고싶습니다!! 좋은 강의 감사합니다!!
상황 정리 저희가 매장 관리자용 통합 예약 관리 시스템 을 개발하고 있습니다. 현재 시스템 구조 [외부 예약 플랫폼 (네이버 같은)] ↓ 자동 연동 [매장 POS 시스템 (티오더 같은)] ↓ API 폴링 [우리 통합 관리 시스템] 고객이 외부 플랫폼에서 예약하면 POS에 자동으로 들어옴 우리는 POS API를 폴링해서 예약 데이터를 가져옴 우리 어드민에서 직접 예약 등록 기능도 곧 추가 예정 문제 상황 POS 업체에 확인해보니: API에서 외부 플랫폼 예약 구분이 안 됨 → 구분값 추가 예정 외부 예약은 원칙적으로 수정 불가 현재는 수정이 되지만 플랫폼 API 키가 연결 안 되어 사실상 불가능 DB만 바꿔서는 의미 없고, 외부 플랫폼 API 재호출이 필요 POS와 외부 플랫폼 간 연동이 아직 불완전한 상태 논의된 두 가지 방향 A안 (readonly 방식) - 우리 어드민 생성 예약 → POS 직접 등록 (수정 가능) - 외부 플랫폼 예약 → POS에서 폴링해서 조회만 (수정 불가) - UI에서 "외부 예약" 표시하고 수정 버튼 비활성화 - 수정 필요시 원본 플랫폼 바로가기 링크 제공 B안 (통합 수정 방식) - 모든 예약을 우리 시스템에서 직접 수정 가능하게 - 외부 예약 수정 시 POS API → 외부 플랫폼 API 호출까지 처리 장단점 분석 A안 장점 각 시스템의 책임 범위가 명확함 동기화 정합성 이슈 없음 외부 플랫폼 정책 변경에 영향 안 받음 여러 플랫폼을 한 곳에서 조회만 해도 관리자 리소스 절감 효과 A안 단점 수정은 여전히 각 플랫폼에서 해야 함 "진정한 통합 관리"는 아님 B안 장점 완전한 통합 관리 경험 관리자가 한 곳에서 모든 예약 제어 B안 단점 외부 플랫폼 API 직접 연동 불가 (계약 주체가 매장) POS가 외부 플랫폼 제어 API를 제공해야 하는데 현재 없음 POS-외부 플랫폼 양쪽 동기화 복잡도 높음 외부 플랫폼 정책(취소규칙 등) 변경 시 계속 대응 필요 문제 발생 시 책임 소재 애매 질문 이런 다중 오리진 데이터를 통합하는 시스템 에서: readonly로 가는 게 맞을까요, 아니면 수정까지 구현해야 할까요? 단계적으로 접근한다면 어떤 순서가 좋을까요? 1단계: 통합 조회만 2단계: POS가 API 제공하면 그때 수정 추가 비슷한 사례에서 일반적으로 어떻게 접근하나요? 현재 상황에서는 A안(readonly)이 합리적이라고 판단됩니다. 하지만 시간이 지나서 다음 조건들이 충족된다면: POS에서 외부 예약 플랫폼 식별값을 제공 외부 예약 플랫폼 API를 저희가 제공받을 수 있음 그때는 어떤 아키텍처로 가야 할까요? 옵션 1: 직접 호출 방식 [우리 시스템] → [외부 플랫폼 API] (직접 호출) → [POS API] (동기화용) 우리가 외부 플랫폼 API를 직접 호출 POS는 조회 + 동기화 확인용으로만 사용 옵션 2: POS Proxy 방식 [우리 시스템] → [POS API] → [외부 플랫폼 API] POS가 외부 플랫폼 제어 API를 제공하도록 요청 우리는 POS API만 호출하면 POS가 내부적으로 플랫폼 API 처리 외부 플랫폼 변경사항은 POS가 책임 옵션 3: readonly 유지 [우리 시스템] → [POS API] (조회만) 기술적으로 가능해져도 readonly 유지 각 플랫폼 바로가기만 제공 어떤 방식이 일반적이고, 각 옵션의 trade-off는 무엇인가요? 특히 옵션 1 vs 옵션 2에서: 우리가 직접 여러 외부 API를 관리하는 게 나을까요? 아니면 POS가 Proxy/Gateway 역할을 하게 하는 게 나을까요?
git에 있는 .claude를 프로젝트 파일세 추가하고, 'User 도메인을 만들고 회원가입, 조회 등 관련 API를 만들어줘. ' 라고 동일하게 터미널에 입력시 얼추 강의와 비슷하게는 만들어지는데 (여러번 돌려봤는데) 예를들면 dto에 record가 생성은 되는데 네이밍이 아예 다르다거나, 네이밍은 그럴 수 있는데 내용이 아예 달라서(예: Annotation이 전혀 없이 아래와 같이 생성 되거나 다른 파일의 내용이 다르거나 하는 경우, Service에 애노테이션이 아예 없거나, Controller에도 메서드가 반절도 안만들어졌다거나, resources>http>user.sh파일 내용에 내용이 반절이상 없다거나.. 등 package com.apiece.springboot_sns_sample.controller.dto; import com.apiece.springboot_sns_sample.domain.user.User; public record UserRegisterRequest(String email, String password, String username) { public User toEntity(String encodedPassword) { return User.builder().email(email).password(encodedPassword).username(username).build(); } } 이럴 경우 어떻게 강의를 수강해 나가면 좋을지 궁금합니다.
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? 예 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예 3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예 [질문 내용] update하고 응답할 때 어차피 request dto에 응답할 파라메터 값이 담겨져있는데 해당 값을 response dto에 할당해서 응답하면 되지 않을까요? 즉 제가 구상한 코드는 다음과 같습니다. @Operation(summary = "회원 수정") @PutMapping("{id}") public UpdateMemberResponse updateMemberV1(@Validated @RequestBody MemberUpdateDTO memberUpdateDTO, BindingResult bindingResult, @PathVariable("id") Long id){ memberService.update(memberUpdateDTO, id); return new UpdateMemberResponse(id, memberUpdateDTO.getName(), memberUpdateDTO.getCity(), memberUpdateDTO.getStreet(), memberUpdateDTO.getZipcode()); } 굳이 컨트롤러단에서 memberService.findOne(id)를 select문을 날려서 호출해서 준영속상태의 Member 엔티티의 값을 response dto에 할당하는 이유가 있을까요?
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예) [질문 내용] 클린도 해보고 다시 시도해보아도 test때문에 실패 한다네요.. 왜이럴까요..??
Claude Code활용(Skills, Hooks)에서 hook과 관련된 설정(예: spotless)내용은 깃 주소에 올려진 프로젝트의 build.gradle.kts에 반영이 되어 있지 않은 것 같아요. hooks경로에 lint.sh는 생성이 되어 있는데 말이죠. settings.loca.json 파일도 없어요. 강의에 관련 설명만 존재하기 떄문에 git에 올려진 프로젝트에 업데이트를 해주시면 어떨까 싶습니다.
.claude 폴더가 어떻게 해당 프로젝트에 생성되게하나요? 해당 프로젝트에서 claude실행한다고 생성되지는 않는 것 같은데.. .claude폴더가 생성되는게 어떤 것에 의해 기본적으로 생성될 때 agents, commands, hooks, skill는 생성이 되지 않고, 필요에 의해 각 경로를 생성하고 skills안에 spring-api-rules와 같은 원하는 것들(주로 어떤 항목들에 대한 것들을 넣으시는지도 궁금합니다.)을 넣으면 되는지 궁금합니다. 해당 프로젝트에 폴더로 생성되는 것과 맥북의 root(?) 폴더에 .claude로 생성되는 것에 어떤 차이가 있나요?
========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? 아니오 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예 3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예 [질문 내용] 솔직히 강의에 관련된 질문과는 거리가 먼 내용이어서 질문을 해야 하나 말아야 하나 고민하다가 결국 질문하는 게 좋겠다고 생각되어 질문을 올립니다. 강의 중에 강사님께서 중간중간에 test관련 공부를 열심히 해야한다고 하셨는데 혹시 어떤 식으로 공부해야 하는지 그리고 강사님께서는 어떻게 하셨는지, 또 추천하시는 서적이 있다면 그 서적의 제목이 어떻게 되는지 알고 싶습니다.
학습 관련 질문을 최대한 상세히 남겨주세요! 고민 과정도 같이 나열해주셔도 좋습니다. 먼저 유사한 질문이 있었는지 검색해보세요. 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요! 20:15 경 작성해주신 findByPath 의 함수에서 article_id 의 경우 함께 조회할 필요가 없나요? 이전에 생성했던 unique index 구조를 생각해보면 article_id 와 path 2개로 복합 인덱스를 생성하였기 때문에 게시글마다 path 에 대한 unique 함을 보장하는 것이기 때문에 조회 시에 findByArticleIdAndPath 과 같은 식으로 해야 할 것 같은데, 제가 잘못 이해한 부분이 있는지 궁금합니다!
PPT - 11page 2장 max.jsx => main 아닌가요? ( 강의 영상이라..수정이.. 암튼 그러핟고요_ 예전에 제가 "코드로 배우는 스프링 웹 프로젝트" (개정판) ..ㅋㅋ 도서에도 오타 잡는 취미가 있었다는 ㅎㅎㅎ ^^ 다른 분이 찾으셨다면.. . .. 중복 Q&A 문의 죄송합니다.