Skip to content

Commit eca259c

Browse files
committed
perf(loan): eliminate duplicate book fetch in LoanService.create()
Load all books upfront into a Map before loan creation loop, replacing two findById calls per book with a single fetch. - Collect distinct bookIds into Map<Long, Book> before processing - Remove validation loop, merging existence check into single stream - Reuse cached Book from map in creation loop - Add .distinct() to guard against duplicate bookIds in request
1 parent d83a038 commit eca259c

File tree

1 file changed

+37
-35
lines changed

1 file changed

+37
-35
lines changed

src/main/java/com/example/library/loan/LoanService.java

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -48,47 +48,49 @@ public class LoanService {
4848
// CRIAR EMPRÉSTIMO
4949
// ─────────────────────────────────────────────
5050

51-
@Transactional
52-
public LoanResponseDTO create(LoanCreateDTO dto) {
53-
54-
User user = getAuthenticatedUser();
55-
56-
// Valida existência de todos os livros antes de iniciar
57-
for (Long bookId : dto.booksId()) {
58-
bookAvailabilityPort.findById(bookId)
59-
.orElseThrow(() -> new BookNotFoundException(bookId));
60-
}
51+
@Transactional
52+
public LoanResponseDTO create(LoanCreateDTO dto) {
53+
54+
User user = getAuthenticatedUser();
55+
56+
// Carrega todos os livros de uma vez — elimina double fetch
57+
Map<Long, Book> books = dto.booksId().stream()
58+
.distinct()
59+
.collect(Collectors.toMap(
60+
id -> id,
61+
id -> bookAvailabilityPort.findById(id)
62+
.orElseThrow(() -> new BookNotFoundException(id))
63+
));
6164

6265
log.info("Creating loan for user={} books={}", user.getEmail(), dto.booksId());
63-
64-
Loan loan = new Loan();
65-
loan.setUser(user);
66-
loan.setLoanDate(LocalDate.now());
67-
loan.setDueDate(LocalDate.now().plusDays(7));
68-
loan.setStatus(LoanStatus.WAITING_RETURN);
6966

70-
for (Long bookId : dto.booksId()) {
71-
Book book = bookAvailabilityPort.findById(bookId)
72-
.orElseThrow(() -> new BookNotFoundException(bookId));
67+
Loan loan = new Loan();
68+
loan.setUser(user);
69+
loan.setLoanDate(LocalDate.now());
70+
loan.setDueDate(LocalDate.now().plusDays(7));
71+
loan.setStatus(LoanStatus.WAITING_RETURN);
72+
73+
for (Long bookId : dto.booksId()) {
74+
Book book = books.get(bookId);
7375

7476
// Update atômico — evita race condition em empréstimos concorrentes
75-
int updated = bookAvailabilityPort.decrementCopies(bookId);
76-
if (updated == 0) {
77-
throw new BookNotAvailableException(bookId, book.getTitle());
78-
}
79-
80-
LoanItem item = new LoanItem();
81-
item.getId().setBookId(book.getId());
82-
item.setLoan(loan);
83-
item.setBook(book);
84-
item.setQuantity(1);
85-
86-
loan.getItems().add(item);
77+
int updated = bookAvailabilityPort.decrementCopies(bookId);
78+
if (updated == 0) {
79+
throw new BookNotAvailableException(bookId, book.getTitle());
80+
}
81+
82+
LoanItem item = new LoanItem();
83+
item.getId().setBookId(book.getId());
84+
item.setLoan(loan);
85+
item.setBook(book);
86+
item.setQuantity(1);
87+
88+
loan.getItems().add(item);
8789
log.debug("Book added to loan: bookId={} title={}", bookId, book.getTitle());
88-
}
90+
}
8991

9092
Loan saved = loanRepository.save(loan);
91-
93+
9294
// Publica evento — outros domínios podem reagir sem acoplamento direto
9395
eventPublisher.publishEvent(new LoanCreatedEvent(
9496
saved.getId(),
@@ -98,10 +100,10 @@ public LoanResponseDTO create(LoanCreateDTO dto) {
98100

99101
log.info("Loan created: loanId={} user={} books={}",
100102
saved.getId(), user.getEmail(), dto.booksId().size());
101-
103+
102104
// Recarrega com JOIN FETCH para garantir que o mapper acessa itens dentro da transação
103105
return mapper.toDTO(findWithItemsOrThrow(saved.getId()));
104-
}
106+
}
105107

106108
// ─────────────────────────────────────────────
107109
// DEVOLVER EMPRÉSTIMO

0 commit comments

Comments
 (0)