학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) [질문 내용] 여기에 질문 내용을 남겨주세요. 회원-주문 관계에서 하나의 회원은 여러 주문을 할수있어서 1:N이라 하셨는데 서버 관점에서 보면 여러 회원이 존재하기 때문에 N:M으로 설계해야 하지 않을까요?? 고객 한명당 트랜잭션 관점으로 설계를 해야하는건지 기준이 따로 있는지 궁금합니다..
안녕하세요. member.getTeam()과 team.getMember()의 동작원리에 대해 이해가 되지않는 부분이 있어서, 구글링을 통해 아래와 같이 정리해보았는데 이렇게 이해하는게 맞을까요? member.getTeam() member.getTeam()을 호출하면, JPA는 내부적으로 MEMBER테이블에서 현재 member의 JoinColumn으로 명시했던 TEAM_ID값(=외래키값)을 가져와서 TEAM테이블의 기본키를 참조하여 해당 Team 데이터를 조회하고, Team 객체를 생성해서 반환한다. 1. member.getTeam()이 호출되면 내부적으로 MEMBER테이블에서 현재 member의 JoinColumn으로 명시했던 TEAM_ID값(=외래키값)을 가져온다. 2. Team테이블의 기본키가 이 외래키와 일치하는 Team 데이터를 찾는 SQL쿼리를 실행한다. 3. 조회된 데이터로 Team객체를 생성해서 반환한다. team.getMember() team.getMember()를 호출하면, JPA는 내부적으로 TEAM테이블에서 현재 team의 기본키값을 가져와서 MEMBER테이블의 외래키를 참조하여 해당 Member 데이터를 조회하고, Member객체를 생성해서 기존 비어있는 리스트에 추가한다. 1. team.getMember()가 호출되면 내부적으로 TEAM테이블에서 현재 team의 기본키값을 가져온다. 2. Member테이블의 외래키인 TEAM_ID가 이 기본키와 일치하는 Member 데이터를 찾는 SQL쿼리를 실행한다. ex) SELECT * FROM MEMBER WHERE TEAM_ID = 1; 3. 조회된 데이터로 Member객체를 생성해서 기존 비어있는 리스트에 추가한다.
https://www.inflearn.com/courses/lecture?courseId=326750&unitId=72712&tab=QnA&category=questionDetail&q=672478 다른 분이 글을 올리시긴 했는데 입력이 "문제에 첫 줄에 길이 100을 넘지 않는 공백이 없는 문자열이 주어집니다." 라고 적혀 있지만 예시 입력에도 공백이 있고, next()를 사용하면 틀리는 걸 보니 공백이 있는 문자열로 수정되어야 할 것 같습니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) [질문 내용] 여기에 질문 내용을 남겨주세요. fail(); 부분에서 오류가 뜨고 이텔릭체로 바뀌지 않습니다. 왜 그런가요? package hello.hello_spring.service; import hello.hello_spring.domain.Member; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; class MemberServiceTest { MemberService memberService = new MemberService(); // 테스트케이스에서는 메서드명 한글로 적어도 된다 @Test void 회원가입() { // given Member member = new Member(); member.setName("hello"); // when long saveId = memberService.join(member); // then(검증) Member findMember = memberService.findOne(saveId).get(); assertThat(member.getName()).isEqualTo(findMember.getName()); } @Test // 예외처리(테스트케이스에서는 정상적으로 작동하는거도 좋지만 예외 처리가 더 중요하다) public void 중복_회원_예외() { // given Member member1 = new Member(); member1.setName("spring"); Member member2 = new Member(); member2.setName("spring"); // when memberService.join(member1); try { memberService.join(member2); fail(); } catch (IllegalStateException e) { } // then } @Test void findMembers() { } @Test void findOne() { } }
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 리팩토링 10. 함수를 명령으로 바꾸기 강의에서 6:00 부분에서 설명하시는게 전략패턴 말씀하시는 것 같은데 GoF의디자인 패턴 강의에서 설명해주신 것일까요?? 아직 디자인 패턴 강의는 들어보지 않아서 강사님께서 기억나시냐고 물어보시는게 어떤 부분인지 궁금해서 질문드립니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) [질문 내용] view 계층과 주고 받을 때는 dto로 주고 받아야 하니 들어올 때 form을 사용했는데, 지금처럼 em.createQuery("select i from Item i", Item.class).getResultList(); 이런 repo 코드를 정제없이 그대로 return 해주는 경우 Entity를 직접 전달하고 있는 것 아닌가요 ?? 잘못 이해하고 있는 부분에 대해 짚어주시면 감사하겠습니다. 만약 잘 이해하고 있고, 예제라서 이렇게 처리한 것이라면, 정제하는 과정이 서비스 단에서 일어나는게 좋을까요 ? 컨트롤러 단에서 일어나는게 좋을까요 ?
학습 관련 질문을 최대한 상세히 남겨주세요! 고민 과정도 같이 나열해주셔도 좋습니다. 먼저 유사한 질문이 있었는지 검색해보세요. 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요. select * from article where board_id = 1 order by article_id desc limit 30; 해당 쿼리에서 강사님은 Extra에 null인데 제가 실행하면 using where가 나오네요. using filesort는 안나오는데 크게 상관 없는 부분일까요?
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예) [질문 내용] 안녕하세요. AOP는 핵심 로직이 아닌 부가적인 로직을 처리할 때 주로 사용한다고 알고있습니다. 보통 많이 사용하는 경우는 로그추적, 트랜잭션 등이 있다고 하셨는데 권한을 체크할 때도 사용할 수 있나요? 만약 사용자들은 방을 생성해서 그 방안의 참여자들만 글을 쓸 수 있을 때, 이 유저가 참여자인지 확인하는 로직이 필요하다면 이 로직을 AOP를 활용하여 작성해도 되는지 궁금합니다. 여러 메서드들에서 사용하기 때문에 공통 관심사라는 생각이 들긴 합니다. 직접 서비스단에서 매번 확인하면 중복 코드가 발생할 것 같아서 AOP를 활용하고 싶은데 이런 경우도 사용이 가능한지 궁금해 질문남깁니다. 감사합니다!
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) [질문 내용] 영상보며 차근차근 따라하는데 스프링부트 프로젝트만들고 build.gradle 오픈하니 사진과 같이 에러가 뜹니다. 질문전 다른 분들의 같은 문제를 보았는데 저는 제 어떤 부분이 문제인지 잘모르겠습니다ㅠ..환경변수하고 체크해봤는데 제 눈에는 문제를 잘모르겠어요. 혹시나해서 오류 및 프로젝트 설정, 환경 변수등의 사진들을 올려봅니다. <오류> <Project Structure - Project> 위 사진에서 처음에는 <No SDK>로 설정되어있어서 이것문제인가 싶어 open-jdk로 변경했지만 그대로더군요ㅠ <Project Structure - SDKs> <스프링부트 프로젝트 설정> <환경변수 설정> 이상입니다. 도움좀 주세요ㅠ 스프링 공부하고싶어여
질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? 예 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예 3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예 [질문 내용] 강사님 안녕하세요. 프로젝트를 진행하다가 Querydsl의 fetcjoin을 쓰고, 로직 처리를 위해 엔티티를 따로 조회하기 위해 다음과 같이 코드를 짰는데, 중간에 for 문으로 c.getUser()했을 떄는 fetcjoin이 제대로 되어서 쿼리가 발생하지 않고, userId가 1인 UserEntity가 잘 조회됩니다. 그런데 이미 userId가 1인 user 엔티티가 조회 됐음에도 불구하고 맨 아래의 userId가 1인 User findBy를 사용하여 조회하면 select UserEntity where userId=1 인 한 번 더 쿼리가 발생합니다. 원래 fetcjoin는 엔티티.get연관엔티티() 에서만 작동을 하는 걸까요? for 문 돌면서 id 체크하고 c.getUser()로 재사용하기에는 비효율적일 것 같은데 지금과 같은 상황에서는 어떤 식으로 쿼리가 한 번더 발생하지 않으면서 동일한 이미 fetcjoin으로 가져온 엔티티를 사용할 수 있는 좋은 방법이 있는지 궁금합니다. fetcjoin 한 getPopUpStoreComments querydsl 코드는 위와 같습니다. 감사합니다.
안녕하세요 선생님 배운 테스트 강의를 통해 사이드 프로젝트를 하며 성장 중입니다. 동시성 테스트를 하던 중 문제가 발생 하였고 해결은 하였으나 다른 방법이 있는지 조언을 구하고자 질문을 남기게 되었습니다. 먼저 엔티티에 대한 설명을 드리겠습니다. 모임과 모임 참여 테이블이 1 : N 모임 참여 회원 테이블이 N : 1 입니다. 모임은 최대 인원인 capacity를 가지고 있습니다. 여러 회원이 모임에 동시 참여 했을때 인원수를 제한 되는지 보기 위한 테스트 입니다. 동시성 문제를 막기 위해 비관적락을 모임을 조회 할때 사용 하였습니다. DB는 MYSQL을 사용 하였습니다. 트랜잭션의 격리 수준은 기본인 REPEATABLE READ 을 사용 하였습니다. 테스트 트랜잭션에서 모임을 저장 하고 비동기 작업의 다른 스레드에서 모임 조회를 시도 할 경우 테스트의 모임 저장은 커밋되지 않은 트랜잭션으로 조회가 불가능한 문제가 있었습니다. 문제 해결 방안으로는 테스트 트랜잭션 어노테이션 제거 비동기 작업 스레드를 통한 트랜잭션 커밋으로 이후 트랜잭션이 모임을 조회 하는 방법 테스트의 일관성을 위해 2번 방법을 선택 하였습니다. 모임과 회원을 저장하는 부분을 비동기 작업 스레드를 통해 커밋 하고 이후 스레드의 트랜잭션으로 조회 하여 데이터를 읽을수 있도록 하였습니다. 테스트는 정상적으로 통과 되었지만 문제가 발생 했습니다. 스레드 작업의 트랜잭션 커밋으로 다른 테스트에 영향이 가는 문제 입니다. 원인은 테스트 트랜잭션이 전파 되지 않음에 따라 스레드 작업이 롤백 되지 않았습니다. @Test void not_executors_Tx() { log.info("외부 트랜잭션 시작"); TransactionStatus outer = txManager.getTransaction (new DefaultTransactionAttribute()); log.info("outer.isNewTransaction={}", outer.isNewTransaction()); log.info("내부 트랜잭션 시작"); TransactionStatus inner = txManager.getTransaction (new DefaultTransactionAttribute()); log.info("inner.isNewTransaction={}", inner.isNewTransaction()); } 결과 : outer.isNewTransaction = true / inner.isNewTransaction = true @Test void executors_Tx() { //given log.info("외부 트랜 잭션 시작"); ExecutorService executorService = Executors.newFixedThreadPool(5); TransactionStatus outer = txManager.getTransaction (new DefaultTransactionAttribute()); log.info("outer.isNewTransaction() = {}", outer.isNewTransaction()); log.info("내부 트랜 잭션 시작"); executorService.submit(() -> { TransactionStatus inner = txManager.getTransaction (new DefaultTransactionAttribute()); log.info("inner.isNewTransaction() = {}", inner.isNewTransaction()); }); executorService.shutdown(); } 결과 : outer.isNewTransaction = true / inner.isNewTransaction = false 커밋한 스레드의 결과로 인해 전체 테스트에 영향이 가게 되었습니다. 밑의 페이징 정보 조회 테스트를 실행 하기 전에 모임 전체 조회를 해보니 테스터 라는 스레드 작업에서 저장한 모임이 조회 되었습니다. 어떻게 하면 커밋된 데이터들이 다른 테스트에 영향을 주지 않을까?를 고민 하였고 생각한 해결 방안은 @AfterEach를 사용 하자 였습니다. 하지만 해결 되지 않았습니다. 다른 테스트 에선 여전히 커밋된 테스터 모임이 발견 되었습니다. 이 부분은 왜 delete가 되지 않았는지 모르겠습니다. 추측 하기로는 REPEATABLE READ 격리 수준에서 자신이 트랜잭션을 시작 하였을때 데이터만 조회 하고 삭제 할수 있기 때문에 테스트 트랜잭션 에서는 스레드가 추가한 데이터를 조회, 삭제가 불가능 해서 발생한 문제 이지 않을까 하는 추측 입니다. 그레서 모든 테스트에 @BeforeEach를 사용 하여 테스트 시작전에 데이터를 모두 지우고 테스트 하니 통과 하였습니다. 선생님께 드리고 싶은 질문은 이렇게 해결 한것이 최선인지 궁금합니다. 멀티 스레드는 테스트 할때 어떻게 동작 할지 모르기 때문에 트랜잭션 어노테이션을 제거 하는 방법이 더 나을까요? @AfterEach void cleanUp() { userEventRepository.deleteAllInBatch(); bookmarkRepository.deleteAllInBatch(); eventRepository.deleteAllInBatch(); userRepository.deleteAllInBatch(); } @DisplayName("페이징 정보를 받아 모임을 조회 합니다.") @Test void getPagingEvents() { //given for (int i = 0; i < 10; i++) { Event event = createEvent("테스터" + i, "자전거 모임"); eventRepository.save(event); } Pageable pageable = PageRequest.of(1, 3); //when Slice<EventPreviewResponseDto> slice = eventService.getPagingEvents(pageable); //then assertThat(slice.getContent()) .extracting("author") .containsExactlyInAnyOrder("테스터3", "테스터4", "테스터5"); } @DisplayName("5명의 회원이 동시에 최대 인원이 3명인 모임에 참가 할때 3명만 참여 할 수 있다.") @Test void joinEventWhenParticipateAtTheSameTimeWithConcurrency() throws Exception { //given int taskCount = 5; ExecutorService executorService = Executors.newFixedThreadPool(5); CountDownLatch countDownLatch = new CountDownLatch(taskCount); Event findEvent = executorService.submit(() -> eventRepository.save(createEvent("테스터", "테스트 모임", 3))).get(); List<User> users = executorService.submit(() -> Stream .generate(() -> { User user = createUser("테스터", "testEmail"); userRepository.save(user); return user; }) .limit(taskCount) .toList()).get(); //when AtomicInteger exceptionCount = new AtomicInteger(0); for (User user : users) { executorService.submit(() -> { try { eventService.joinEvent(findEvent.getId(), user.getId()); eventRepository.flush(); // 엔티티 상태를 DB에 강제로 반영 } catch (BusinessException ex) { exceptionCount.incrementAndGet(); } finally { countDownLatch.countDown(); // 카운트다운 } }); } countDownLatch.await(); Long participateCount = executorService.submit( () -> userEventRepository.countParticipantByEventId(findEvent.getId())).get(); executorService.shutdown(); //then assertThat(participateCount).isEqualTo(3); assertThat(exceptionCount.get()).isEqualTo(2); }
영한님 안녕하세요. 문제와 풀이 2번을 풀고 고민하고 강의를 학습해도 약간의 의문점이 생겨서 질문드립니다. Thread t1 = new Thread(new MyTask(), "t1"); Thread t2 = new Thread(new MyTask(), "t2"); Thread t3 = new Thread(new MyTask(), "t3"); t1.start(); t2.start(); t3.start(); // 1초 t1.join(); t2.join(); t3.join(); // 대기 현재 위와 같은 코드에서 main 쓰레드가 t1, t2, t3에게 작업을 시킵니다. 그리고 t1의 join을 만나니까 t1이 작업이 완전히 끝날때까지 대기상태에 빠집니다. 그리고 t1이 작업이 완료되면 t2.join()이 실행된다면 결과가 아래와 같이 나와야 하는데 왜 이렇게 안 되는지 의문입니다. ㅠㅠ t1: 1 t1: 2 t1: 3 t2: 1 t2: 2 t2: 3 ...
*ptr++의 경우에는 arr의 값도 증가 시키고 포인터 위치도 변경시키는거고, ++*ptr 일경우에는 바라보고 있는 포인터위치는 변하지 않고 arr의 값만 증가 시키고 끝나는건가요? 제가 지금 이해하고 있는건 *ptr 의 앞에 "++"이 붙으면 값만 선증가시키고, "++"이 뒤에 붙이면 값을 후증가시키고 메모리값도 증가시킨다. 인데 이게 맞나요?
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. package DJ_LEE.Array; import java.util.Scanner; public class Main10 { public int solutionOfLecture(int[][] arr, int num) { int answer =0; int min = 0; for (int i=1; i<= num; i++) { int cnt = 0; for (int j = 1; j <= num; j++) { for (int k = 1; k <= 5; k++) { if (arr[i][k] == arr[j][k]) { cnt++; break; } } } if(cnt>min){ min=cnt; answer=i; } } return answer; } public static void main(String[] args) { Main10 T = new Main10(); Scanner kb = new Scanner(System.in); int num = kb.nextInt(); int[][] arr = new int[num+1][6]; for( int i=1; i<=num; i++){ for(int j =1; j<=num; j++){ int el = kb.nextInt(); arr[i][j]=el; } } System.out.println(T.solutionOfLecture(arr,num)); } }
학습 관련 질문을 남겨주세요. 어떤 부분이 고민인지, 무엇이 문제인지 상세히 작성하면 더 좋아요! 먼저 유사한 질문이 있었는지 검색해 보세요. 서로 예의를 지키며 존중하는 문화를 만들어가요. 안녕하세요. 개인 프로젝트를 진행하다 궁금한 점이 있어 질문드립니다. 현재, 서로 다른 두 트랜잭션이 동시에 실행될 때 발생할 수 있는 동시성 문제를 해결하기 위해 락을 적용해두었습니다. 테스트 코드에서는 락이 없는 경우에는 문제 상황이 발생하고, 락 적용 시에는 정상 작동함을 보여주고자 합니다. 인터넷에서 관련 자료를 찾아본 결과, Executor 와 CountDownLatch 를 활용하여 동시성 문제를 테스트하는 방식을 많이 사용하길래, 저도 이를 적용해 테스트를 구성하려 하고 있습니다. 멀티스레딩 환경에서는 실행 순서가 보장되지 않으므로, 운이 나쁘면 두 트랜잭션이 순차적으로 실행될 수도 있다고 생각했고 실행 순서를 제어하여 문제가 발생할 수 있는 상황을 코드로 만들어야 한다고 생각했습니다. 그러나 Executor 와 CountDownLatch 만으로는 트랜잭션 단위로 묶인 작업들의 실행 순서를 제어하기 어려워, 원하는 동시성 문제 시나리오를 재현하는 데 한계가 있었습니다. wait , notify 등의 스레드 제어 메서드를 활용하면 원하는 실행 순서를 만들 수 있을 것 같지만, 이것이 최선의 방법인지 고민이 되어 질문드립니다. 선생님께서는 동시성 문제 테스트를 진행할 때, 실행 순서를 제어하여 문제가 발생하는 시나리오를 인위적으로 만들고 이런 상황에서도 정상 작동함을 확인하시나요? 아니라면 동시성 문제를 테스트할 때 어떤 방법을 사용하시는지 궁금합니다
학습 관련 질문을 최대한 상세히 남겨주세요! 고민 과정도 같이 나열해주셔도 좋습니다. 먼저 유사한 질문이 있었는지 검색해보세요. 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 좋아요 수 구현 부분 강의를 듣고 따라하던 도중 문제가 생겼습니다. ArticleLikeCount의 엔티티에서 version에 @Version을 붙이고 난 후, 테이블의 값을 모두 삭제하고 나서 테스트를 돌리면 에러가 발생합니다. 계속해서.. Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): 위 에러가 계속 발생하네요. 테스트는 like 메서드를 실행했습니다. 이상하게도 강사님 코드로는 정상 작동이 되어서, 그대로 복사해서 붙여넣기로 가져와서 돌려보면 안되네요. 테이블에 데이터가 이미 존재하면 그때부터는 정상적으로 되는 것 같은데, 테이블이 비어있으면 에러가 발생합니다 ㅜㅜ 혹시라도 확인하실 수 있도록 제 프로젝트 파일을 압축해서 올려놓은 링크 공유하겠습니다. ㅜㅜ https://drive.google.com/file/d/1H9UR9UXZhgmBH-XrXoXPc2-dgrixF7b-/view?usp=drive_link