Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/main/java/org/example/Book.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ public void setTitle(String title) {
public Long getId() {
return bookId;
}

public Loan getLoan() {
return loan;
}

public void setLoan(Loan loan) {
this.loan = loan;
}

@ManyToMany
@JoinTable(name = "book_author",
joinColumns = @JoinColumn(name = "bookId"))
Expand Down
23 changes: 8 additions & 15 deletions src/main/java/org/example/Loan.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package org.example;

import jakarta.persistence.*;

import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Entity
public class Loan {
Expand All @@ -16,27 +12,24 @@ public class Loan {

private ZonedDateTime loanDate;
private ZonedDateTime returnDate;
private Long userId;
private Long bookId;

public Loan(){
}


public Long getBookId() {
return bookId;
public User getUser() {
return user;
}

public void setBookId(Long bookId) {
this.bookId = bookId;
public void setUser(User user){
this.user = user;
}

public Long getUserId() {
return userId;
public Book getBook() {
return book;
}

public void setUserId(Long userId) {
this.userId = userId;
public void setBook(Book book){
this.book = book;
}

public ZonedDateTime getReturnDate() {
Expand Down
76 changes: 76 additions & 0 deletions src/main/java/org/example/LoanServices.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.example;

import jakarta.persistence.EntityManager;
import java.time.ZonedDateTime;
import java.util.List;

public class LoanServices {

final private EntityManager em;

public LoanServices(EntityManager em) {
this.em = em;
}

// Kolla om en bok är utlånad
public boolean isBookLoaned(Long bookId) {

List<Loan> loans = em.createQuery(
"SELECT l FROM Loan l WHERE l.book.bookId = :bookId AND l.returnDate IS NULL",
Loan.class
)
.setParameter("bookId", bookId)
.getResultList();

if (loans.isEmpty()) {
return false;
} else {
return true;
}
}

// Låna en bok
public boolean loanBook(Long bookId, Long userId) {

if (isBookLoaned(bookId)) {
return false;
}

User user = em.find(User.class, userId);
Book book = em.find(Book.class, bookId);

Loan loan = new Loan();

loan.setUser(user);
loan.setBook(book);
loan.setLoanDate(ZonedDateTime.now());
loan.setReturnDate(null);
em.persist(loan);
return true;
}
Comment on lines +33 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Owning-side not updated, missing transaction and null checks.

Several issues in this method:

  1. Relationship not persisted: Book is the owning side (@JoinColumn), so you must call book.setLoan(loan) for the FK to be saved—loan.setBook(book) alone won't persist the association.
  2. No transaction: em.persist() requires an active transaction.
  3. No null checks: em.find() returns null if the entity doesn't exist—dereferencing will cause NPE.
🐛 Proposed fix
     public boolean loanBook(Long bookId, Long userId) {

         if (isBookLoaned(bookId)) {
             return false;
         }

         User user = em.find(User.class, userId);
         Book book = em.find(Book.class, bookId);

+        if (user == null || book == null) {
+            return false;
+        }
+
+        em.getTransaction().begin();
+
         Loan loan = new Loan();

         loan.setUser(user);
         loan.setBook(book);
         loan.setLoanDate(ZonedDateTime.now());
-        loan.setReturnDate(null);
+        book.setLoan(loan);  // Set owning side to persist the FK
+
         em.persist(loan);
+        em.getTransaction().commit();
         return true;
     }
🤖 Prompt for AI Agents
In @src/main/java/org/example/LoanServices.java around lines 33 - 50, In
loanBook(Long bookId, Long userId) ensure you 1) validate em.find results (User
user and Book book) and return false or throw if either is null; 2) start and
commit/rollback a transaction around the persistence (use EntityTransaction tx =
em.getTransaction()/tx.begin()/tx.commit() and tx.rollback() on exceptions); and
3) update the owning side before persisting by calling book.setLoan(loan) (in
addition to loan.setBook(book)) so the @JoinColumn is saved; also wrap
persistence in try/catch to rollback the transaction on error.


// Lämna tillbak en bok
public boolean returnBook(User user, Book book) {

Loan loan = em.createQuery(
"SELECT l FROM Loan l WHERE l.user = :user AND l.book = :book AND l.returnDate IS NULL",
Loan.class
)
.setParameter("user", user)
.setParameter("book", book)
.getResultStream()
.findFirst()
.orElse(null);

if (loan == null) {
return false;
}

em.getTransaction().begin();

loan.setReturnDate(ZonedDateTime.now());
book.setLoan(null);
em.getTransaction().commit();
return true;
}
Comment on lines +53 to +75

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing rollback handling and inconsistent API design.

  1. No rollback on failure: If an exception occurs between begin() and commit(), the transaction is left dangling. Wrap in try-catch or use try-with-resources with a transaction wrapper.
  2. Inconsistent API: loanBook(Long, Long) takes IDs while returnBook(User, Book) takes entities. Consider aligning the signatures for consistency.
🔧 Suggested fix with rollback handling
     public boolean returnBook(User user, Book book) {

         Loan loan = em.createQuery(
             "SELECT l FROM Loan l WHERE l.user = :user AND l.book = :book AND l.returnDate IS NULL",
             Loan.class
         )
             .setParameter("user", user)
             .setParameter("book", book)
             .getResultStream()
             .findFirst()
             .orElse(null);

         if (loan == null) {
             return false;
         }

-        em.getTransaction().begin();
-
-        loan.setReturnDate(ZonedDateTime.now());
-        book.setLoan(null);
-        em.getTransaction().commit();
-        return true;
+        try {
+            em.getTransaction().begin();
+            loan.setReturnDate(ZonedDateTime.now());
+            book.setLoan(null);
+            em.getTransaction().commit();
+            return true;
+        } catch (Exception e) {
+            if (em.getTransaction().isActive()) {
+                em.getTransaction().rollback();
+            }
+            throw e;
+        }
     }
🤖 Prompt for AI Agents
In @src/main/java/org/example/LoanServices.java around lines 53 - 75, The
returnBook method currently starts a transaction with
em.getTransaction().begin() but does not rollback on exception and also uses
entity parameters unlike loanBook(Long, Long); update returnBook(User, Book) to
wrap the transaction in a try-catch-finally (or use a transaction helper) so
that on any exception you call em.getTransaction().rollback() before rethrowing
or returning false, and ensure the transaction is committed only on success;
additionally align the API by either adding a returnBook(Long userId, Long
bookId) overload (mirroring loanBook) or change loanBook to accept entities so
both methods use the same parameter style and lookup logic (refer to returnBook,
loanBook, em, Loan, setReturnDate).

}