2424import com .example .library .user .UserRepository ;
2525import com .example .library .user .exception .UserNotFoundException ;
2626
27+ import lombok .RequiredArgsConstructor ;
28+
29+ @ RequiredArgsConstructor
2730@ Service
2831public class LoanService {
2932
@@ -34,13 +37,6 @@ public class LoanService {
3437 private final UserRepository userRepository ;
3538 private final LoanMapper mapper ;
3639
37- public LoanService (LoanRepository loanRepository , BookRepository bookRepository , UserRepository userRepository , LoanMapper mapper ) {
38- this .loanRepository = loanRepository ;
39- this .bookRepository = bookRepository ;
40- this .userRepository = userRepository ;
41- this .mapper = mapper ;
42- }
43-
4440 // ─────────────────────────────────────────────
4541 // CRIAR EMPRÉSTIMO
4642 // ─────────────────────────────────────────────
@@ -63,7 +59,6 @@ public LoanResponseDTO create(LoanCreateDTO dto) {
6359 loan .setStatus (LoanStatus .WAITING_RETURN );
6460
6561 for (Long bookId : dto .booksId ()) {
66-
6762 Book book = bookRepository .findById (bookId )
6863 .orElseThrow (() -> new BookNotFoundException (bookId ));
6964
@@ -89,7 +84,8 @@ public LoanResponseDTO create(LoanCreateDTO dto) {
8984 log .info ("Loan created: loanId={} user={} books={}" ,
9085 saved .getId (), user .getEmail (), dto .booksId ().size ());
9186
92- return mapper .toDTO (loan );
87+ // Recarrega com JOIN FETCH para garantir que o mapper acessa itens dentro da transação
88+ return mapper .toDTO (findWithItemsOrThrow (saved .getId ()));
9389 }
9490
9591 // ─────────────────────────────────────────────
@@ -100,7 +96,7 @@ public LoanResponseDTO create(LoanCreateDTO dto) {
10096 public LoanResponseDTO returnLoan (Long loanId ) {
10197
10298 User user = getAuthenticatedUser ();
103- Loan loan = find (loanId );
99+ Loan loan = findWithItemsOrThrow (loanId );
104100
105101 validateOwnershipOrAdmin (loan , user );
106102
@@ -135,7 +131,7 @@ public LoanResponseDTO returnLoan(Long loanId) {
135131 public LoanResponseDTO cancelLoan (Long loanId ) {
136132
137133 User user = getAuthenticatedUser ();
138- Loan loan = find (loanId );
134+ Loan loan = findWithItemsOrThrow (loanId );
139135
140136 validateOwnershipOrAdmin (loan , user );
141137
@@ -177,32 +173,26 @@ public void markOverdue() {
177173 @ Transactional (readOnly = true )
178174 public LoanResponseDTO findById (Long loanId ) {
179175 User user = getAuthenticatedUser ();
180- Loan loan = find (loanId );
181-
176+ Loan loan = findWithItemsOrThrow (loanId );
182177 validateOwnershipOrAdmin (loan , user );
183178 return mapper .toDTO (loan );
184179 }
185180
186181 @ Transactional (readOnly = true )
187182 public List <LoanResponseDTO > findMyLoans () {
188-
189183 User user = getAuthenticatedUser ();
190-
191184 log .debug ("Fetching loans for user={}" , user .getEmail ());
192-
193- return loanRepository .findByUserId (user .getId ())
185+ return loanRepository .findByUserIdWithItems (user .getId ())
194186 .stream ()
195187 .map (mapper ::toDTO )
196188 .toList ();
197189 }
198190
199191 @ Transactional (readOnly = true )
200192 public List <LoanResponseDTO > findByUser (Long userId ) {
201-
202193 userRepository .findById (userId )
203194 .orElseThrow (() -> new UserNotFoundException (userId ));
204-
205- return loanRepository .findByUserId (userId )
195+ return loanRepository .findByUserIdWithItems (userId )
206196 .stream ()
207197 .map (mapper ::toDTO )
208198 .toList ();
@@ -218,7 +208,7 @@ public List<LoanResponseDTO> findOverdue() {
218208
219209 @ Transactional (readOnly = true )
220210 public List <LoanResponseDTO > findAll () {
221- return loanRepository .findAll ()
211+ return loanRepository .findAllWithItems ()
222212 .stream ()
223213 .map (mapper ::toDTO )
224214 .toList ();
@@ -228,10 +218,14 @@ public List<LoanResponseDTO> findAll() {
228218 // HELPERS PRIVADOS
229219 // ─────────────────────────────────────────────
230220
231- private Loan find (Long loanId ) {
232- return loanRepository .findById (loanId )
233- .orElseThrow (() -> new LoanNotFoundException (loanId ));
234- }
221+ /**
222+ * Busca um empréstimo pelo ID com itens e usuário já carregados via JOIN FETCH.
223+ * Garante que o mapper acessa as coleções LAZY dentro da transação ativa.
224+ */
225+ private Loan findWithItemsOrThrow (Long loanId ) {
226+ return loanRepository .findByIdWithItemsAndUser (loanId )
227+ .orElseThrow (() -> new LoanNotFoundException (loanId ));
228+ }
235229
236230 /**
237231 * Recupera o usuário autenticado direto do SecurityContext.
@@ -245,14 +239,15 @@ private User getAuthenticatedUser() {
245239
246240 /**
247241 * Garante que apenas o dono do empréstimo ou um ADMIN pode operá-lo.
242+ * Retorna 404 intencionalmente para não vazar que o empréstimo existe.
248243 */
249244 private void validateOwnershipOrAdmin (Loan loan , User user ) {
250245 boolean isAdmin = user .getRoles ().contains ("ROLE_ADMIN" );
251246 boolean isOwner = loan .getUser ().getId ().equals (user .getId ());
252247
253248 if (!isOwner && !isAdmin ) {
254249 log .warn ("Unauthorized loan access attempt: loanId={} userId={}" , loan .getId (), user .getId ());
255- throw new LoanUnauthorizedException (loan .getId ()); // 404 intencional — não vazar que o loan existe
250+ throw new LoanUnauthorizedException (loan .getId ());
256251 }
257252 }
258253}
0 commit comments