inblog logo
|
SHIN
    TeamProject

    2차 팀 프로젝트 : BookBox (Back-End)

    도서 대여/예약 앱 플랫폼 : 예약, 관리자
    SHIN's avatar
    SHIN
    Oct 22, 2024
    2차 팀 프로젝트 : BookBox (Back-End)
     
    Error: Response 응답 데이터 관련
    Admin 로그인 후 발급받은 JWT 토큰으로 전체 회원 리스트 조회 시
    notion image
     

    관리자 로그인 및 토큰 발급

    notion image
    notion image
     

    발급받은 토큰으로 유저 목록 조회

    notion image
    // Lazy 로딩 사용 시 발생하는 에러 {"reason": Could not write JSON: failed to lazily initialize a collection of role: green.mtcoding.bookbox.user.User.lends: could not initialize proxy - no Session}
     

    원인

    • lends 컬렉션을 직렬화할 때 Hibernate 세션이 닫힌 상태에서 접근하려고 하여 발생한 것..?
    • 컨트롤러가 데이터를 직렬화하여 JSON 반환 시점에 이미 Hibernate 세션이 종료됨. → lends 컬렉션 접근 시 No Session 오류 발생

    해결1

    • User Entity에서 @OneToMany → Eager Fetching으로 변경
    @OneToMany(mappedBy = "user", fetch = FetchType.EAGER) private List<Lend> lends = new ArrayList<>();
    → 이 방법은 User Entity 조회 시 lends 컬렉션도 즉시 로딩됨. 하지만 Eager Loading은 성능에 영향을 미쳐서 필요한 경우에만 사용

    해결2

    • 명시적 초기화를 통해 Lazy Loading 처리
    public List<User> getUserList() { List<User> users = userRepository.findAll(); users.forEach(user -> Hibernate.initialize(user.getLends())); // Lazy 로딩 명시적 초기화 return users; }
     
     

    해결3

    • UserDTO 생성
    @Data public static class UserDTO { private Long id; private String username; private String email; private String phone; // 매개변수로 받음 public UserDTO(User user) { this.id = user.getId(); this.username = user.getUsername(); this.email = user.getEmail(); this.phone = user.getPhone(); } }
    • AdminService에서 DTO 변환
    @Transactional public List<UserRequest.UserDTO> getUserList() { List<User> users = userRepository.findAll(); return users.stream() .map(UserRequest.UserDTO::new) // .collect(Collectors.toList()); }
     

    Stream API

    • List 같은 컬렉션에서 데이터를 처리할 때 반복문 대신 사용
    return users.stream() .map(UserRequest.UserDTO::new) // 각 User 객체를 UserDTO로 변환 .collect(Collectors.toList()); // 변환된 UserDTO들을 다시 List로 수집

    users.stream():

    • users는 List<User> 형식의 컬렉션
    • stream()은 이 리스트를 Stream 객체로 변환. Stream은 데이터를 일종의 "흐름"으로 다룰 수 있게 해줌
    • Stream API는 데이터를 처리하는 다양한 기능(필터링, 매핑, 정렬 등)을 제공

    2. map(UserRequest.UserDTO::new):

    • map()은 Stream 내의 각 요소를 다른 타입으로 변환하는 역할
    • 여기서 UserRequest.UserDTO::new = User 객체를 UserDTO로 변환. → new UserRequest.UserDTO(user)와 같은 의미
    • 즉, 각각의 User 객체를 UserDTO로 변환하여 새로운 스트림 생성

    3. collect(Collectors.toList()):

    • collect()는 변환된 스트림 데이터를 최종적으로 리스트 형태로 수집하는 메소드
    • Collectors.toList()는 변환된 UserDTO 객체들을 다시 리스트로 모아서 반환
      • notion image
     
     
     
    예약(Reservation) 로직 설계

    1. 예약 가능 여부 확인

    • 예약 인원수는 최대 3명, 현재 해당 책에 대해 예약이 다 차 있으면 예약 불가능
    1-1.ReservationRepository - 해당 도서에 대한 현재 예약수 조회 쿼리
    @Query("select count(r) from Reservation r where r.book.isbn13 = :isbn13 and r.cancleData is null") int countCurrentReservations(@Param("isbn13") String isbn13);
    1-2. 예약 등록
     
    Error: 대여중인 도서 반납 부분 테스트 오류 - 해결완료

    1. 더미 데이터로 조회

    1-1. userId 1번 (ssar) 으로 로그인
    notion image
     
    1-2. 발급받은 토큰으로 현재 대여중인 도서 목록 조회
    notion image
     
    1-3. 조회한 목록 중 임의의 isbn13을 body 데이터에 실어서 해당 도서에 대한 반납 요청
    notion image
     
    💡
    • 사진처럼 대여중인 도서에 대한 반납이 안됨
     
     
     
     
    예약 로직 테스트 순서 (Postman)

    1. user4로 로그인 후 임의의 도서 대여

    notion image
     
    notion image
     
    notion image
    notion image
     
     

    2. user5로 로그인 후 user4가 대여한 도서에 대해서 예약하기

    notion image
     
    notion image
     
     
    notion image
    notion image
     
     

    3. user4는 대여한 책을 반납 → 이때 user5가 자동으로 예약에서 대여로 업데이트 되는지 확인

     
    notion image
     
     
    notion image
    notion image
     

    4. 자동으로 업데이트 된 것을 확인 후 user6로 로그인 후 2~3번 반복

    Error 1
    • user6으로 예약 시
    {"reason": 이 도서는 현재 대여 가능한 상태라 예약이 불가능합니다.}
     
    문제 원인(예상)
    • lendCount가 반납 후 자동 대여 시 제대로 업데이트되지 않아서 발생
    • 반납 후 예약자가 대여할 때 lendCount 를 1로 업데이트해야 하지만, 자동 대여 로직에서 이 부분이 누락된 것 같음
     
    해결 방안
    • 자동 대여 로직에서 책이 대여 중인 상태로 바뀌면, lendCount도 함께 업데이트해야 함
     
    ReservationService
    // 반납 시 자동 대여 로직 @Transactional public void 자동대여(String isbn13) { // 첫 번째 예약자 확인 Reservation firstReservation = reservationRepository.findReservationsByBook(isbn13) .stream() .findFirst() .orElse(null); if (firstReservation != null) { // 대여 처리 Lend newLend = new Lend(); newLend.setUser(firstReservation.getUser()); newLend.setBook(firstReservation.getBook()); // 대여 날짜를 현재 시간으로 설정 newLend.setLendDate(Timestamp.valueOf(LocalDateTime.now())); newLend.setReturnDate(Timestamp.valueOf(LocalDateTime.now().plusDays(7))); // 기본적으로 7일 대여 기간 lendRepository.save(newLend); // 예약 정보 삭제 reservationRepository.delete(firstReservation); // 예약 순번 업데이트 reservationRepository.updateReservationSequences(isbn13, firstReservation.getSequence()); // 책의 대여 상태와 대여 카운트를 업데이트 Book book = firstReservation.getBook(); book.setLendStatus(true); // 대여 중 상태로 변경 book.setLendCount(1); // 대여 카운트를 1로 설정 bookRepository.save(book); // 수정된 책 정보를 저장 } }
     
    LendController
    // 반납하기 @PutMapping("/api/lends/return") public ResponseEntity<?> lendReturn(@RequestHeader("Authorization") String token, @RequestBody LendRequest.ReturnDTO request) { String jwtToken = token.replace("Bearer ", ""); Long userId = JwtUtil.extractUserIdFromToken(jwtToken); LendResponse.ReturnDTO result = lendService.직접반납하기(userId, request); // 책의 대여 상태를 업데이트 (lendCount를 0으로 설정) Book book = bookRepository.findById(request.getIsbn13()) .orElseThrow(() -> new ExceptionApi404("책을 찾을 수 없습니다.")); book.setLendStatus(false); // 대여 가능한 상태로 변경 book.setLendCount(0); // 대여 카운트를 0으로 설정 bookRepository.save(book); return ResponseEntity.ok(Resp.ok(result, "성공적으로 반납되었습니다.")); }
     
    정상적으로 출력
    { "status": 200, "msg": "대여되었습니다. 내서재에서 대여된 도서를 확인하세요.", "body": { "lendId": 22, "lendDate": "2024-10-15T16:06:22.798+09:00", "returnDate": "2024-10-22T16:06:22.798+09:00" } } // Book의 lend count 확인
     

    확인 사항 - 호정씨 요청

    1. 예약 취소 시 reservation_tb에 해당 컬럼 삭제 되는지 확인
      1. notion image
    💡
    • 추후 예약 취소 내역 조회등을 위해 history 남김
      • notion image
        notion image
     
     
    1. 첫 번째 순서인 예약자에게 대여처리 됐을 때, 해당 도서의 두 번째 순서였던 예약자의 예약 순위 업데이트 되는지 확인 → 자동반납 로직에 추가
     
    • 현재 대여중인 user4와,
    notion image
    notion image
     
    • 해당 도서를 순서대로 예약한 user5와 user6
    notion image
    notion image
     
     

    user4 대여자가 반납 시

    notion image
    1. Return_Status는 True로 변경되고, 예약 순번이 1인 user5 예약자가 자동으로 대여됨
    notion image
     
    1. 동시에 user6 인 예약자의 예약 순번은 2에서 1로 자동 업데이트 됨
    notion image
     

     
     

    자동 반납 및 예약 기능 테스트 (대여기간 만료)

     
    1. user4 = 대여, user5, user6 = 차례대로 예약
    notion image
    notion image
     
    notion image
    notion image
     
    1. 대여 기간 만료 및 자동 반납 스케줄링 확인
      1. 대여 기간이 끝나는 시점(+7D)에서 스케줄링된 자동 반납이 실행되야 함
      2. @Scheduled 대신 수동으로 자동반납() 메소드를 호출하는 API 임시 생성
        1. @PutMapping("/api/lends/auto-return") public ResponseEntity<?> triggerAutoReturn() { lendService.자동반납(); // LendService의 자동반납 메소드 호출 return ResponseEntity.ok("대여기간이 만료되어 자동으로 반납처리 되었습니다."); }
      3. 이 API 실행 시 자동 반납 처리 및 예약 순번 1인 예약자(user5)에게 자동 대여
      4. Error
        notion image
        1. 자동 반납은 대여상태(returnStatus=false)인 도서 중에서 반납일이 현재 날짜와 일치 하는 경우에만 실행
         
        1. 즉 lend_tb에서 returnDate가 현재 날짜와 일치하고 returnStatus = false인 대여 내역이 있어야만 자동반납()이 호출됨
         
        1. 테스트를 위해 자동 반납을 강제로 실행할 데이터 쿼리문을 대여 후에 작성(h2에 직접 작성)
          1. update lend_tb set return_date = CURRENT_DATE where user_id = 4;
            notion image
            notion image
         
         
        1. 현재 날짜로 설정한 후 자동 반납 요청 시 정상적으로 반납 및 예약자에 대한 대여 업데이트가 이루어지고,
          1. notion image
            notion image
         
         
        1. 예약자 순번도 업데이트 완료
          1. notion image
            notion image
         
         
        notion image
         
         
     
    데이터 바인딩 - 관리자 (보류)

    관리자 페이지 설명

     
    1. admin/bookmanagement/_components/BookManagement_body.dart
    💡
    • 역할
      • 도서 관리 페이지에서 전체 도서 목록을 표시
      • 검색 기능을 통해 도서를 조회
      • 도서 추가와 수정 기능도 포함
    • 구성
      • 검색 기능
        • 사용자가 도서를 검색할 수 있도록 검색창과 검색 결과 표시
        • 검색어가 입력되면 _performSearch() 메소드에서 해당 검색어를 기반으로 필터링된 bookList를 업데이트
      • 보유 도서 목록
        • 보유한 도서의 목록을 테이블 형식으로 화면에 출력
        • 목록은 ListView.builder()를 사용하여 동적으로 생성, 각 도서 항목마다 수정과 삭제 버튼이 포함
      • 도서 추가 버튼
        • 페이지 하단에 있는 FloatingActionButton을 통해 새로운 도서를 등록할 수 있는 페이지로 이동
     
    1. admin/bookmanagement/bookedit/book_edit_page.dart
    💡
    • 역할
      • 선택한 도서의 세부 정보를 수정
      • 기존에 입력된 도서 정보를 TextField로 보여주고, 사용자가 수정한 데이터 저장 가능
    • 구성:
      • 도서 수정
        • 사용자가 제목, 저자, 출판일, 출판사 정보를 수정 가능
        • 이미지 수정 기능도 포함
      • 이미지 업로드
        • 이미지 선택 버튼을 통해 갤러리에서 이미지 선택, 선택된 이미지를 미리 보기로 보여
        • PermissionHandler를 사용해 갤러리 접근 권한을 요청
      • 저장 로직
        • _saveBook() 메소드를 통해 수정된 데이터를 저장
        • API 호출을 통해 서버에 변경된 데이터를 전달할 수 있는 준비
     
    1. admin/bookmanagement/bookregister/book_add_page.dart
    💡
    • 역할
      • 새로운 도서를 등록 페이지
      • 사용자는 새로운 도서의 제목, 저자, 출판일, 출판사를 입력하고, 이미지를 업로드하여 저장 가능
    • 구성:
      • 도서 추가
        • 사용자가 입력한 정보들을 TextField로 받음
        • 책 표지도 업로드 가능
      • 이미지 업로드
        • 도서 표지 이미지를 선택하여 미리 보기 제공
        • API를 통해 서버에 이미지를 업로드하는 로직을 작성 가능
      • 저장 로직
        • _saveBook() 메소드를 통해 새로운 도서를 저장
        • 추가된 도서 정보를 서버에 전송할 준비
     
    1. admin/bookmanagement/BookManagement.dart
    💡
    • 역할
      • 도서 관리의 전체 레이아웃을 담당하고 BookManagementBody를 포함
      • 실제 도서 목록 및 검색 기능을 제공하는 BookManagementBody를 출력하는 역할
    • 구성
      • BookManagementBody() 위젯을 사용하여 도서 목록 및 검색 기능을 렌더링
     
    1. admin/bookmanagement/BookManagement_vm.dart
    💡
    • 역할
      • BookManagement 관련된 데이터 및 비즈니스 로직을 처리하는 뷰 모델
      • 이곳에서 API 요청을 관리, 도서 목록 데이터를 가져와 UI에 전달하는 역할
    • 구성:
      • 도서 정보 클래스 (Book)
        • 도서에 대한 정보(ISBN, 제목, 저자, 출판일, 출판사, 표지 이미지 등)를 저장하는 모델
      • 도서 목록 (bookList)
        • 샘플 데이터를 제공, 추후 이 부분에 실제 데이터 바인딩을 통해 서버로부터 받아온 데이터를 업데이트 가능
     
     
    Error: 관리자-도서 수정 요청 에러 (해결)

    도서 등록 후 해당 등록된 도서 수정 후 요청을 할때 Error Return

    notion image
     
     

    문제 원인

    💡
    • isbn13으로 도서 조회 시 도서가 정상적으로 조회되지 않거나 입력 값 잘못 전달 가능성
     

    1. isbn13 확인

    💡
    • AdminController에서 @PathVariable로 전달되는 isbn13 값이 Book_tb의 isbn13값과 일치해야 조회 가능
    • 즉 해당값이 db의 isbn13 값과 일치하지 않아서 해당 Error Msg가 반환 됐을 수 있음
    • Controller의 해당 매핑부분에 로그 추가
    @PutMapping("/api/admins/{isbn13}/update") public ResponseEntity<?> updateBook(@PathVariable String isbn13, @RequestBody BookRequest.UpdateDTO updateDTO) { System.out.println("Received ISBN13: " + isbn13); // isbn13 값 확인 BookResponse.BookDetailDTO updateBook = bookService.도서업데이트(isbn13, updateDTO); return ResponseEntity.ok(Resp.ok(updateBook)); }
    notion image
    → 정상적으로 출력됨
     

    2. BookRepository에서 isbn13 조회 문제

    💡
    1. 현재 BookRespository에서 findById 메소드 사용중
    1. 이 메소드는 기본적으로 Long 타입의 ID로 조회되는 것이 기본적이므로 String 타입의 isbn13을 사용할 때 문제가 될 수 있음
    • BookService 수정 (findById → findByIsbn13)
    // 도서 수정 public BookResponse.BookDetailDTO 도서업데이트(String isbn13, BookRequest.UpdateDTO dto) { Book book = bookRepository.findByIsbn13(isbn13) .orElseThrow(() -> new ExceptionApi400("도서를 찾을 수 없습니다.")); // findByIsbn13 사용 // 카테고리 조회 Category category = categoryRepository.findById(dto.getCategoryId()) .orElseThrow(() -> new ExceptionApi400("카테고리를 찾을 수 없습니다.")); // 도서 정보 업데이트 book.update(dto, category); bookRepository.save(book); return new BookResponse.BookDetailDTO(book); }
     
    1. BookRepository 확인
    // 카테고리 조회 Category category = categoryRepository.findById(dto.getCategoryId()) .orElseThrow(() -> new ExceptionApi400("카테고리를 찾을 수 없습니다."));
    → 이렇게 해도 여전히 update 요청 시 도서를 못 찾음. 그래서 혹시 등록이 잘 된 건가 싶어서 해당 isbn13으로 조회를 해보니..
    notion image
    아예 등록조차 안된것.
     

    해결

    💡
    • BookService에서 해당 등록과 수정 로직 상단에 @Transactional 등록하니 해결…
    • Transaction 부분을 다시 제대로 짚어야 겠다.
    Share article

    SHIN

    RSS·Powered by Inblog