Error: Response 응답 데이터 관련
Admin 로그인 후 발급받은 JWT 토큰으로 전체 회원 리스트 조회 시

관리자 로그인 및 토큰 발급


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

// 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
객체들을 다시 리스트로 모아서 반환

예약(Reservation) 로직 설계
Error: 대여중인 도서 반납 부분 테스트 오류 - 해결완료
예약 로직 테스트 순서 (Postman)
1. user4로 로그인 후 임의의 도서 대여




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




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



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 확인
확인 사항 - 호정씨 요청
- 예약 취소 시 reservation_tb에 해당 컬럼 삭제 되는지 확인

- 추후 예약 취소 내역 조회등을 위해 history 남김


- 첫 번째 순서인 예약자에게 대여처리 됐을 때, 해당 도서의 두 번째 순서였던 예약자의 예약 순위 업데이트 되는지 확인 → 자동반납 로직에 추가
- 현재 대여중인 user4와,


- 해당 도서를 순서대로 예약한 user5와 user6


user4 대여자가 반납 시

- Return_Status는 True로 변경되고, 예약 순번이 1인 user5 예약자가 자동으로 대여됨

- 동시에 user6 인 예약자의 예약 순번은 2에서 1로 자동 업데이트 됨

자동 반납 및 예약 기능 테스트 (대여기간 만료)
- user4 = 대여, user5, user6 = 차례대로 예약




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

update lend_tb set return_date = CURRENT_DATE where user_id = 4;







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

문제 원인
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));
}

→ 정상적으로 출력됨
2. BookRepository
에서 isbn13
조회 문제
- 현재
BookRespository
에서findById
메소드 사용중
- 이 메소드는 기본적으로 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);
}
- BookRepository 확인
// 카테고리 조회
Category category = categoryRepository.findById(dto.getCategoryId())
.orElseThrow(() -> new ExceptionApi400("카테고리를 찾을 수 없습니다."));
→ 이렇게 해도 여전히 update 요청 시 도서를 못 찾음.
그래서 혹시 등록이 잘 된 건가 싶어서 해당 isbn13으로 조회를 해보니..

아예 등록조차 안된것.
해결
- BookService에서 해당 등록과 수정 로직 상단에
@Transactional
등록하니 해결…
- Transaction 부분을 다시 제대로 짚어야 겠다.
Share article