Skip to content

Younesbranch#11

Open
gitnes94 wants to merge 27 commits intomainfrom
younesbranch
Open

Younesbranch#11
gitnes94 wants to merge 27 commits intomainfrom
younesbranch

Conversation

@gitnes94
Copy link

@gitnes94 gitnes94 commented Jan 9, 2026

Summary by CodeRabbit

  • New Features

    • Added interactive booking management system with create, view, update, and delete functionality
    • Implemented guest and table management features
    • Added booking status tracking (pending, confirmed, cancelled, completed, no-show)
    • Integrated persistent database backend with MySQL
  • Chores

    • Added Docker Compose configuration for local MySQL database setup
    • Updated project dependencies for database connectivity and ORM support
    • Updated project documentation with completion status

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

This PR implements a complete restaurant booking system backend with JPA/Hibernate integration. It introduces four new entity classes (Booking, Guest, Table, TimeSlot), a BookingService for CRUD operations, refactors the main application to provide an interactive booking menu, and configures MySQL persistence via Docker Compose.

Changes

Cohort / File(s) Summary
Infrastructure & Configuration
.gitignore, docker-compose.yml, pom.xml, src/main/resources/META-INF/persistence.xml
Added .env to gitignore. Introduced Docker Compose MySQL 9.5.0 service with persistent volume. Added three Maven dependencies: HikariCP, hibernate-hikaricp, and jakarta.persistence-api. Created persistence unit "restaurantPU" with MySQL datasource configuration and Hibernate settings.
JPA Entity Classes
src/main/java/org/example/entity/Booking.java, BookingStatus.java, Guest.java, Table.java, TimeSlot.java
Added five new entities: Booking (with ManyToOne relations to TimeSlot and Table, ManyToMany to Guest), BookingStatus enum with PENDING/CONFIRMED/CANCELLED/COMPLETED/NO_SHOW states, Guest (with bidirectional ManyToMany to Booking), Table (with OneToMany to Booking), and TimeSlot. Includes state transition methods and relationship management.
Service Layer
src/main/java/org/example/entity/service/BookingService.java
Introduced BookingService with guest creation, booking creation with validation (capacity, date range, overlap checks), status updates, deletion, and retrieval methods for all entities. Includes transactional handling and error handling via IllegalArgumentException.
Application Bootstrap & Interactive Menu
src/main/java/org/example/App.java
Replaced simple startup with full Hibernate/JPA bootstrap using ClassGraph entity discovery and HikariCP datasource. Added dynamic main menu system with CRUD operations for bookings, data initialization logic, guest/table/timeslot viewing, and input validation with 3-month booking window constraint.
Documentation
README.md
Added review assignment due date badge and marked Vecka 1 checklist items as completed.

Sequence Diagram(s)

sequenceDiagram
    Actor User
    participant App
    participant BookingService
    participant EntityManager
    participant Database

    User->>App: Select "Create Booking"
    App->>App: Gather input (date, party size, guest)
    App->>BookingService: createBooking(tableId, timeSlotId, date, partySize, guestIds)
    BookingService->>EntityManager: Query table by ID
    EntityManager->>Database: SELECT from Tables
    Database-->>EntityManager: Table data
    EntityManager-->>BookingService: Table object
    BookingService->>BookingService: Validate capacity & availability
    BookingService->>EntityManager: Query existing bookings
    EntityManager->>Database: SELECT from Bookings
    Database-->>EntityManager: Booking data
    BookingService->>BookingService: Check for overlaps
    BookingService->>EntityManager: Create & persist Booking
    EntityManager->>Database: INSERT into Bookings
    Database-->>EntityManager: Confirmation
    EntityManager-->>BookingService: Success
    BookingService-->>App: Booking created
    App-->>User: Display confirmation
Loading
sequenceDiagram
    Actor User
    participant App
    participant BookingService
    participant EntityManager
    participant Database

    User->>App: Select "View Bookings"
    App->>BookingService: getAllBookings()
    BookingService->>EntityManager: Query all bookings with joins
    EntityManager->>Database: SELECT FROM Bookings with relations
    Database-->>EntityManager: Booking records with nested data
    EntityManager-->>BookingService: List<Booking>
    BookingService-->>App: Bookings with guests, tables, timeslots
    App->>App: Format & display results
    App-->>User: Show booking list
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 Hops through schemas with glee!
Tables and guests join the spree,
HikariCP pools the spring flow,
Docker containers steal the show!
A booking system built with care,
From database to user's affair! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Younesbranch' appears to be a branch name rather than a descriptive summary of the changes. It does not convey meaningful information about what was implemented. Replace with a descriptive title that summarizes the main change, such as 'Add restaurant booking service with JPA entities and Docker MySQL integration' or similar.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🤖 Fix all issues with AI agents
In @.gitignore:
- Line 3: The .gitignore currently excludes docker-compose.yml which likely
prevents committing the shared dev DB setup; decide and act accordingly: if
docker-compose.yml is the canonical, team-wide development compose file, remove
it from .gitignore so it is committed and instead add
docker-compose.override.yml and .env to .gitignore to keep per-developer
overrides/private envs out of repo; if the intention is the opposite (compose
not shared), update the project README to explain how to obtain the canonical
compose file and keep docker-compose.yml ignored. Reference files:
docker-compose.yml, docker-compose.override.yml, .env, and .gitignore.

In @docker-compose.yml:
- Around line 1-17: The docker-compose service "mysql" currently hardcodes
MYSQL_ROOT_PASSWORD and exposes port 3306 publicly; replace the hardcoded
password with environment-variable interpolation (e.g., MYSQL_ROOT_PASSWORD:
${MYSQL_ROOT_PASSWORD:-root123}) and add optional MYSQL_USER and MYSQL_PASSWORD
entries using similar ${VAR:-default} expansion so credentials can be
overridden, and restrict the exposed port binding in the "ports" entry to
localhost (e.g., 127.0.0.1:3306:3306) to avoid binding to all interfaces; keep
the service name "mysql", container_name "restaurant_booking_db", and volume
mapping intact while documenting or adding a .env file for secure overrides.

In @src/main/java/org/example/App.java:
- Line 29: The code calls booking.confirm() but the Booking class exposes
confirmBooking(); replace the invalid call to booking.confirm() with
booking.confirmBooking(), or alternatively add a confirm() delegating method in
the Booking class that invokes confirmBooking(); update any imports/compile
targets accordingly so the call resolves.
- Around line 18-19: The code incorrectly instantiates a non-existent class
RestaurantTable; replace the creation with the actual entity Table and call its
constructor using the correct parameter order (capacity first, then tableNumber)
— e.g., construct new Table(5, "T2") instead of new RestaurantTable("T2", 5) —
then persist the variable (table2) as before with em.persist(table2).

In @src/main/java/org/example/entity/Booking.java:
- Around line 101-103: The setter setParty currently self-assigns the field
(this.partySize = partySize) so the partySize never updates; change it to assign
the incoming parameter by setting this.partySize = party inside the setParty(int
party) method (optionally add basic validation there if needed).
- Around line 129-139: Booking.toString() currently prints the full table and
guest objects (fields table and guests) which cross-reference bookings and can
cause infinite recursion/StackOverflow; change toString() to avoid traversing
those objects — e.g., print table identifiers (table.getId() or null) and either
guests size or a list of guest ids instead of the guest objects themselves;
update the toString() in the Booking class (method toString, fields id, date,
timeSlot, partySize, table, guests) to use table.getId() and
guests.stream().map(Guest::getId).collect(...) or guests.size() so the
representation is safe.
- Around line 85-87: The setter setTime(LocalDate time) contains a
self-assignment (this.date = date) so the field is never updated; update the
method in class Booking to assign the parameter to the field (e.g., this.date =
time) or rename the parameter to match your intended semantics and assign
accordingly so the date field is updated when setTime(...) is called.

In @src/main/java/org/example/entity/Table.java:
- Around line 69-77: Table.toString() currently includes the bookings collection
which, combined with Booking.toString() referencing table, causes infinite
recursion; update Table.toString() to omit the full bookings objects and instead
log a safe representation (e.g., bookings.size() or a list of booking IDs) so
the bookings field is no longer expanded recursively; modify the toString()
method in class Table (and optionally adjust Booking.toString() similarly) to
reference only non-recursive data (size/IDs) rather than the bookings collection
itself.
- Around line 79-89: The current Table.equals() and Table.hashCode() use mutable
fields (bookings) and the database id, causing identity/hash instability and
lazy-loading issues; change both methods to use a stable business key instead
(e.g., tableNumber) only (or tableNumber plus any other immutable business
attribute if needed), remove bookings and id from equality and hash
calculations, and implement null-safe comparisons for tableNumber so
equals/hashCode remain consistent before and after persistence and do not touch
lazy collections.

In @src/main/resources/META-INF/persistence.xml:
- Line 6: The persistence unit declaration for "restaurantPU" is missing an
explicit transaction-type and should be set to RESOURCE_LOCAL for Java SE/CLI
usage; update the <persistence-unit name="restaurantPU"> element to include
transaction-type="RESOURCE_LOCAL" so the persistence provider uses local
transactions rather than JTA.
- Around line 12-16: The persistence.xml currently hardcodes DB credentials
(properties jakarta.persistence.jdbc.url, jakarta.persistence.jdbc.user,
jakarta.persistence.jdbc.password) and uses the root user; remove these secrets
from the committed file and switch to environment-driven or externalized
configuration (e.g., reference JNDI/DataSource or resolve from environment
variables) so credentials are not in source control and the app uses a non-root
DB user matching your docker-compose setup; update persistence.xml to omit the
plaintext user/password and wire the connection via the application
server/DataSource or environment lookup instead.
🧹 Nitpick comments (7)
src/main/resources/META-INF/persistence.xml (1)

21-26: Treat hbm2ddl.auto=update and SQL logging as dev-only settings.

If you’re going to run this beyond local dev, consider moving these into a dev-specific profile / runtime override (and using validate in non-dev). Also confirm the Hibernate property names you’re using match your Hibernate version (especially hibernate.highlight_sql).

pom.xml (1)

19-27: Inconsistent indentation in dependency declarations.

The newly added dependencies at lines 19-27 and 63-67 use different indentation (8 spaces) compared to existing dependencies at lines 47-62 (4 spaces). Consider aligning all dependencies consistently.

Also applies to: 63-67

src/main/java/org/example/entity/TimeSlot.java (2)

12-16: Consider using LocalTime instead of String for time fields.

Using String for startTime and finishTime loses type safety and makes time comparison/validation harder. Consider using java.time.LocalTime.

Also, the unique = true constraint on each field individually means no two time slots can share the same start OR the same finish time. If the intent is to ensure unique slot combinations, a composite unique constraint would be more appropriate.

♻️ Proposed refactor
-    @Column(nullable = false, unique = true)
-    private String startTime;
+    @Column(nullable = false)
+    private LocalTime startTime;

-    @Column(nullable = false, unique = true)
-    private String finishTime;
+    @Column(nullable = false)
+    private LocalTime finishTime;

And add a composite unique constraint at the class level:

@Entity
@jakarta.persistence.Table(
    name = "time_slots",
    uniqueConstraints = @UniqueConstraint(columnNames = {"startTime", "finishTime"})
)
public class TimeSlot {

5-59: Missing equals() and hashCode() overrides.

JPA entities used in collections (like the TimeSlot referenced by Booking) should implement equals() and hashCode() based on business keys or the entity identifier. This is consistent with the approach in Table.java.

src/main/java/org/example/entity/Guest.java (2)

5-5: Unused import LocalDateTime.

The java.time.LocalDateTime import is not used in this class.

♻️ Proposed fix
-import java.time.LocalDateTime;

9-77: Missing equals(), hashCode(), and toString() overrides.

For consistency with other entities (like Table) and to ensure proper behavior in collections and debugging, consider adding equals(), hashCode(), and toString() methods.

src/main/java/org/example/entity/Booking.java (1)

43-52: Constructor accepts id parameter for auto-generated field.

The id field is annotated with @GeneratedValue, meaning it's auto-assigned by the database. Accepting it as a constructor parameter and setting it explicitly is unusual and could cause issues if a non-null value is passed. Consider removing id from the constructor or using a separate factory method for reconstruction.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6651b37 and d848b44.

📒 Files selected for processing (11)
  • .gitignore
  • README.md
  • docker-compose.yml
  • pom.xml
  • src/main/java/org/example/App.java
  • src/main/java/org/example/entity/Booking.java
  • src/main/java/org/example/entity/BookingStatus.java
  • src/main/java/org/example/entity/Guest.java
  • src/main/java/org/example/entity/Table.java
  • src/main/java/org/example/entity/TimeSlot.java
  • src/main/resources/META-INF/persistence.xml
🧰 Additional context used
🧬 Code graph analysis (3)
src/main/java/org/example/entity/TimeSlot.java (3)
src/main/java/org/example/entity/Booking.java (1)
  • Entity (9-140)
src/main/java/org/example/entity/Guest.java (1)
  • Entity (9-78)
src/main/java/org/example/entity/Table.java (1)
  • Entity (10-90)
src/main/java/org/example/entity/Table.java (1)
src/main/java/org/example/entity/Booking.java (1)
  • Entity (9-140)
src/main/java/org/example/entity/Booking.java (3)
src/main/java/org/example/entity/Guest.java (1)
  • Entity (9-78)
src/main/java/org/example/entity/Table.java (1)
  • Entity (10-90)
src/main/java/org/example/entity/TimeSlot.java (1)
  • Entity (5-59)
🔇 Additional comments (3)
README.md (2)

1-1: Badge addition looks fine.


13-17: Week 1 checklist updates look consistent.

src/main/java/org/example/entity/BookingStatus.java (1)

1-9: Enum looks good; verify JPA persists it as STRING (not ORDINAL).

Comment on lines +1 to +17
version: '3.8'

services:
mysql:
image: mysql:9.5.0
container_name: restaurant_booking_db
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: restaurant_booking
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped

volumes:
mysql_data:
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Docker official MySQL image available tags 2024 2025

💡 Result:

Recent official Docker Hub mysql tags (examples seen in 2024–2025):

  • MySQL 9.x: 9.5.0, 9.5, 9, innovation, latest (also platform variants like 9.5.0-oraclelinux9, 9-oracle, oraclelinux9). [1]
  • MySQL 8.4.x (8.x branch): 8.4.7, 8.4, 8, lts (and oracle/oraclelinux9 variants). [1]
  • MySQL 8.0.x (patch-tagged): 8.0.44, 8.0 (plus 8.0.44-bookworm, 8.0-debian, and oracle/oraclelinux9 variants). [1]

For the full, up-to-date tag list (including older versions and all platform/OS variants) see the official Docker Hub Tags tab for the mysql image. [1]

Sources:
[1] Docker Hub — mysql (Official Image) Tags.


🌐 Web query:

mysql:9.5.0 Docker Hub official image tag exists

💡 Result:

Yes — the official mysql image on Docker Hub includes the tag "9.5.0". See the tags list on Docker Hub. [1]

You can pull it with:
docker pull mysql:9.5.0. [1][2]

Sources:
[1] Docker Hub — mysql (official) tags.
[2] docker.io image mirror listing for mysql:9.5.0.


🌐 Web query:

mysql:8.4 LTS Docker Hub official image available

💡 Result:

Yes — the official Docker Hub mysql image includes MySQL 8.4 ( LTS ) tags (e.g., 8.4, 8.4.7, lts). Pull with, for example, docker pull mysql:8.4 or docker pull mysql:lts. [1][2]

Sources:
[1] Docker Hub mysql official image (tags list).
[2] MySQL 8.4 release notes (shows 8.4 LTS releases).


mysql:9.5.0 is a valid Docker Hub tag; consider hardening the config for better practices.

The mysql:9.5.0 tag exists on Docker Hub and will pull successfully. The hardcoded MYSQL_ROOT_PASSWORD: root123 and port binding to all interfaces are typical for local development but can be improved:

Suggested compose improvements (credentials/port/user)
 version: '3.8'

 services:
   mysql:
     image: mysql:9.5.0
     container_name: restaurant_booking_db
     environment:
-      MYSQL_ROOT_PASSWORD: root123
-      MYSQL_DATABASE: restaurant_booking
+      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123}
+      MYSQL_DATABASE: ${MYSQL_DATABASE:-restaurant_booking}
+      MYSQL_USER: ${MYSQL_USER:-restaurant_app}
+      MYSQL_PASSWORD: ${MYSQL_PASSWORD:-restaurant_app_pw}
     ports:
-      - "3306:3306"
+      - "127.0.0.1:3306:3306"
     volumes:
       - mysql_data:/var/lib/mysql
     restart: unless-stopped

This allows overriding credentials via environment variables and restricts port exposure to localhost, reducing unintended network access.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
version: '3.8'
services:
mysql:
image: mysql:9.5.0
container_name: restaurant_booking_db
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: restaurant_booking
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
volumes:
mysql_data:
version: '3.8'
services:
mysql:
image: mysql:9.5.0
container_name: restaurant_booking_db
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123}
MYSQL_DATABASE: ${MYSQL_DATABASE:-restaurant_booking}
MYSQL_USER: ${MYSQL_USER:-restaurant_app}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-restaurant_app_pw}
ports:
- "127.0.0.1:3306:3306"
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
volumes:
mysql_data:
🤖 Prompt for AI Agents
In @docker-compose.yml around lines 1 - 17, The docker-compose service "mysql"
currently hardcodes MYSQL_ROOT_PASSWORD and exposes port 3306 publicly; replace
the hardcoded password with environment-variable interpolation (e.g.,
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123}) and add optional
MYSQL_USER and MYSQL_PASSWORD entries using similar ${VAR:-default} expansion so
credentials can be overridden, and restrict the exposed port binding in the
"ports" entry to localhost (e.g., 127.0.0.1:3306:3306) to avoid binding to all
interfaces; keep the service name "mysql", container_name
"restaurant_booking_db", and volume mapping intact while documenting or adding a
.env file for secure overrides.

Comment on lines +69 to +77
@Override
public String toString() {
return "Table{" +
"id=" + id +
", bookings=" + bookings +
", tableNumber='" + tableNumber + '\'' +
", capacity=" + capacity +
'}';
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

toString() includes bookings which can cause infinite recursion.

Table.toString() includes bookings, and Booking.toString() includes table. This creates a circular reference that will cause StackOverflowError when toString() is called on either entity.

🐛 Proposed fix
     @Override
     public String toString() {
         return "Table{" +
             "id=" + id +
-            ", bookings=" + bookings +
+            ", bookingsCount=" + (bookings != null ? bookings.size() : 0) +
             ", tableNumber='" + tableNumber + '\'' +
             ", capacity=" + capacity +
             '}';
     }
🤖 Prompt for AI Agents
In @src/main/java/org/example/entity/Table.java around lines 69 - 77,
Table.toString() currently includes the bookings collection which, combined with
Booking.toString() referencing table, causes infinite recursion; update
Table.toString() to omit the full bookings objects and instead log a safe
representation (e.g., bookings.size() or a list of booking IDs) so the bookings
field is no longer expanded recursively; modify the toString() method in class
Table (and optionally adjust Booking.toString() similarly) to reference only
non-recursive data (size/IDs) rather than the bookings collection itself.

Comment on lines +79 to +89
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Table table = (Table) o;
return capacity == table.capacity && Objects.equals(id, table.id) && Objects.equals(bookings, table.bookings) && Objects.equals(tableNumber, table.tableNumber);
}

@Override
public int hashCode() {
return Objects.hash(id, bookings, tableNumber, capacity);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

equals() and hashCode() include mutable fields and collections.

Including bookings (a mutable collection) and id (which is null before persist) in equals()/hashCode() breaks their contracts:

  1. hashCode changes when the entity is persisted (id goes from null to a value)
  2. hashCode changes when bookings are added/removed
  3. Comparing bookings can trigger lazy loading and cause performance issues

Use business keys (like tableNumber) or a consistent approach for entity identity.

🐛 Proposed fix
     @Override
     public boolean equals(Object o) {
         if (o == null || getClass() != o.getClass()) return false;
         Table table = (Table) o;
-        return capacity == table.capacity && Objects.equals(id, table.id) && Objects.equals(bookings, table.bookings) && Objects.equals(tableNumber, table.tableNumber);
+        return Objects.equals(tableNumber, table.tableNumber);
     }

     @Override
     public int hashCode() {
-        return Objects.hash(id, bookings, tableNumber, capacity);
+        return Objects.hash(tableNumber);
     }
🤖 Prompt for AI Agents
In @src/main/java/org/example/entity/Table.java around lines 79 - 89, The
current Table.equals() and Table.hashCode() use mutable fields (bookings) and
the database id, causing identity/hash instability and lazy-loading issues;
change both methods to use a stable business key instead (e.g., tableNumber)
only (or tableNumber plus any other immutable business attribute if needed),
remove bookings and id from equality and hash calculations, and implement
null-safe comparisons for tableNumber so equals/hashCode remain consistent
before and after persistence and do not touch lazy collections.

xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_2.xsd">
<persistence-unit name="restaurantPU">
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Set transaction-type="RESOURCE_LOCAL" for Java SE / CLI usage to avoid transaction runtime failures.

Proposed change
-    <persistence-unit name="restaurantPU">
+    <persistence-unit name="restaurantPU" transaction-type="RESOURCE_LOCAL">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<persistence-unit name="restaurantPU">
<persistence-unit name="restaurantPU" transaction-type="RESOURCE_LOCAL">
🤖 Prompt for AI Agents
In @src/main/resources/META-INF/persistence.xml at line 6, The persistence unit
declaration for "restaurantPU" is missing an explicit transaction-type and
should be set to RESOURCE_LOCAL for Java SE/CLI usage; update the
<persistence-unit name="restaurantPU"> element to include
transaction-type="RESOURCE_LOCAL" so the persistence provider uses local
transactions rather than JTA.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @src/main/java/org/example/App.java:
- Line 13: The main method signature in class App is declared as "static void
main(String[] args)"; change it to include the public modifier so it reads
"public static void main(String[] args)" to follow Java convention and ensure
the entry point is publicly accessible; update the signature in the App class
where the main method is defined.
- Around line 18-20: Hardcoded DB credentials are present in the builder calls
(.jdbcUrl, .jdbcUsername, .jdbcPassword) which must be externalized; update App
to read the DB URL, username and password from environment variables or a
properties file (e.g., via
System.getenv("DB_URL")/System.getenv("DB_USER")/System.getenv("DB_PASS") or by
loading application.properties with Properties.load and
props.getProperty("db.url"/"db.user"/"db.pass")), then pass those values into
the existing .jdbcUrl(), .jdbcUsername(), and .jdbcPassword() calls and fail
fast with a clear error if any required config is missing.
- Line 164: The default branch in the switch currently calls mainMenu()
recursively which risks StackOverflowError; instead, remove the recursive call
and use continue (or simply let the while loop iterate) to restart the input
loop and optionally print an "Invalid input" message; update the switch default
in the method containing mainMenu() and the surrounding while loop so it does
not call mainMenu() recursively.
- Line 29: Replace the hard-coded hibernate.hbm2ddl.auto value currently set as
property("hibernate.hbm2ddl.auto", "create") so it no longer drops and recreates
the schema on every restart; read the desired strategy from an environment
variable (e.g. HIBERNATE_DDL_AUTO) and default to "update" (or "validate" for
production) so the setting can vary by environment and prevent accidental data
loss.
🧹 Nitpick comments (6)
src/main/java/org/example/entity/BookingStatus.java (1)

10-10: Remove redundant comment.

The comment "// Status system" doesn't add meaningful information beyond what the enum name already conveys.

♻️ Proposed fix
-// Status system
src/main/java/org/example/App.java (5)

24-27: Use consistent language for comments.

The inline comments here are in Swedish (e.g., "Max antal anslutningar", "Minsta antal lediga anslutningar"), while the rest of the codebase uses English. Maintain consistency by using English throughout.


37-47: Reconsider control flow and mainMenu placement.

The mainMenu() call at line 40 blocks execution until the user exits (since it contains a while loop), meaning the TimeSlot query at lines 44-47 only runs after the menu completes. This ordering is unconventional:

  1. Data setup runs first (hours, tables, guests) ✓
  2. Menu blocks execution
  3. TimeSlot query runs only after exit

Typically, you'd display a query result before presenting the interactive menu, or the query would be part of a menu option. The TimeSlot query after the menu appears to be leftover debug code.

Consider moving the query before the menu or removing it:

             hours(emf);
             createTable(emf);
             createGuest(emf);
-            mainMenu();
-
 
             //Show all the hours available
             emf.runInTransaction(em -> {
                 var query = em.createQuery("select h from TimeSlot h");
                 query.getResultList().forEach(System.out::println);
             });
+            
+            mainMenu();

53-63: Consider language consistency in sample data.

The guest descriptions use Swedish phrases ("Bord för fyra", "Bord för 3" meaning "Table for four/3"), while the codebase is primarily in English. For better maintainability and international collaboration, consider using English for all string literals.

Example:

-            Guest guest = new Guest("Gabriela", "Bord för fyra", "072762668");
+            Guest guest = new Guest("Gabriela", "Table for 4", "072762668");
             em.persist(guest);
 
-            guest = new Guest("Samuel", "Bord för 3", "072778882");
+            guest = new Guest("Samuel", "Table for 3", "072778882");

123-138: Use consistent language for comments.

Lines 126-127 contain Spanish comments ("Esto es para scannear todas las entities clases que existen en tal paquete. Es para no hacerlo uno por uno" / "Agregar dependency"), while the rest of the codebase uses English. Maintain consistency by translating these to English.

♻️ Proposed translation
-        //Esto es para scannear todas las entities clases que existen en tal paquete. Es para no hacerlo uno por uno
-        //Agregar dependency
+        // Scan for all entity classes in the package to avoid registering them one by one
+        // Requires ClassGraph dependency

140-167: Menu requires EntityManagerFactory to implement CRUD operations.

The mainMenu() method currently only prints placeholder values and cannot perform actual CRUD operations because it doesn't have access to the EntityManagerFactory. Consider passing emf as a parameter to enable implementation of the menu options.

♻️ Proposed signature change
-    public static void mainMenu() {
+    public static void mainMenu(EntityManagerFactory emf) {
         boolean running = true;
         while (running) {
             String select;

Then update the call site at line 40:

-            mainMenu();
+            mainMenu(emf);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d848b44 and 88194e2.

📒 Files selected for processing (2)
  • src/main/java/org/example/App.java
  • src/main/java/org/example/entity/BookingStatus.java

.property("hibernate.hikari.idleTimeout", "300000") // 5 minuter innan ledig anslutning stängs
.property("hibernate.hikari.connectionTimeout", "20000") // Max väntetid på anslutning (20s)

.property("hibernate.hbm2ddl.auto", "create")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reconsider hibernate.hbm2ddl.auto=create for data persistence.

The "create" strategy drops and recreates the schema on every application restart, causing complete data loss. This is appropriate only for development/testing with disposable data.

For persistent data, use "update" (schema evolution) or "validate" (production). Consider making this configurable per environment.

♻️ Suggested fix
-            .property("hibernate.hbm2ddl.auto", "create")
+            .property("hibernate.hbm2ddl.auto", "update")  // or "validate" for production

Better yet, externalize this setting:

.property("hibernate.hbm2ddl.auto", System.getenv().getOrDefault("HIBERNATE_DDL_AUTO", "update"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.property("hibernate.hbm2ddl.auto", "create")
.property("hibernate.hbm2ddl.auto", "update") // or "validate" for production
🤖 Prompt for AI Agents
In @src/main/java/org/example/App.java at line 29, Replace the hard-coded
hibernate.hbm2ddl.auto value currently set as property("hibernate.hbm2ddl.auto",
"create") so it no longer drops and recreates the schema on every restart; read
the desired strategy from an environment variable (e.g. HIBERNATE_DDL_AUTO) and
default to "update" (or "validate" for production) so the setting can vary by
environment and prevent accidental data loss.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🤖 Fix all issues with AI agents
In @src/main/java/org/example/App.java:
- Line 190: The updateBookingMenu and deleteBookingMenu currently call
Long.parseLong(IO.readln(...)) without handling invalid input; wrap the parse
step in a try/catch for NumberFormatException (same pattern used in
createBookingMenu) around the Long.parseLong call in updateBookingMenu and
deleteBookingMenu, log or display a user-friendly error and abort/return to the
menu on invalid input, and only proceed with the update/delete logic when
parsing succeeds; reference the Long bookingId variable assignment in
updateBookingMenu and the analogous bookingId parsing in deleteBookingMenu to
locate the exact spots to change.
- Around line 141-183: The createBookingMenu flow lacks validation that the
entered partySize fits the selected table's capacity; after parsing partySize,
look up the Table from the earlier "tables" list by matching t.getId() to
tableId (e.g., Table selectedTable = tables.stream().filter(t ->
t.getId().equals(tableId)).findFirst().orElse(null)), then if selectedTable !=
null && partySize > selectedTable.getCapacity() print a clear error like "Error:
Party size (...) exceeds table capacity (...)" and return early before calling
bookingService.createBooking; keep existing exception handling for
createBooking.
- Around line 22-24: The .jdbcUrl, .jdbcUsername and .jdbcPassword calls in
App.java currently use hardcoded credentials; change them to read values from a
secure external source (e.g., System.getenv("DB_URL"), System.getenv("DB_USER"),
System.getenv("DB_PASS")) or from a properties file loaded at startup (e.g.,
load db.properties via App.class.getClassLoader().getResourceAsStream and read
db.url, db.username, db.password), then pass those variables into the
cfg.jdbcUrl(...).jdbcUsername(...).jdbcPassword(...) chain and remove the
hardcoded literals so no credentials remain in source control.
- Around line 82-93: The hours method currently computes end times by string
math which breaks for wrap-around times; update hours(EntityManagerFactory emf)
to parse each start into java.time.LocalTime (e.g., LocalTime.parse(start)),
compute end by startTime.plusHours(2) (or plusMinutes if needed), and use the
LocalTime string or formatted value when constructing the new TimeSlot before
calling em.persist; keep using the same times array and TimeSlot constructor but
replace the manual split/parse/concatenation logic with LocalTime-based
arithmetic to handle day rollovers correctly.
- Around line 105-139: You need to add a utility class named IO because calls
like IO.readln(...) in mainMenu (and elsewhere) cause a compilation error;
implement a final class IO with static methods used across the app: at minimum
public static String readln(String prompt) which prints the prompt, reads a line
from System.in, trims and returns it, and handles EOF/IOException by printing an
error and System.exit(1), and public static int readInt(String prompt) which
repeatedly calls readln(prompt) and parses an int, catching
NumberFormatException to re-prompt on invalid input; ensure methods are
thread-safe, use BufferedReader(new InputStreamReader(System.in)), and do not
throw checked exceptions so existing callers (e.g., mainMenu, createBookingMenu,
updateBookingMenu) compile without modification.

In @src/main/java/org/example/entity/BookingService.java:
- Line 1: The package declaration in BookingService.java ("package
org.example.service;") does not match its current file path under
src/main/java/org/example/entity; move the file to
src/main/java/org/example/service/BookingService.java so the physical location
matches the declared package, or alternatively update the package declaration to
"package org.example.entity;" in the BookingService class to match its current
directory—ensure the class name BookingService remains unchanged and that
imports/other references are updated accordingly.
- Around line 13-15: The BookingService constructor should validate its
EntityManagerFactory parameter to fail fast: in the
BookingService(EntityManagerFactory emf) constructor check that emf is not null
and throw a clear unchecked exception (e.g., IllegalArgumentException or
NullPointerException) with a helpful message if it is null, then assign it to
the field; update any relevant tests if they rely on null construction behavior.
- Around line 93-98: The switch in BookingService that maps newStatus to actions
only handles CONFIRMED, CANCELLED, COMPLETED, and NO_SHOW, leaving
BookingStatus.PENDING unhandled; update the switch (in the method that calls
booking.confirmBooking(), booking.cancelBooking(), booking.completeBooking(),
booking.noShowBooking()) to either add an explicit case for
BookingStatus.PENDING (e.g., booking.markPending() or no-op with a logged
warning) or add a default branch that logs and/or throws for unexpected statuses
so PENDING/unexpected values are not silently ignored.
- Around line 61-66: The loop in BookingService that calls em.find(Guest.class,
guestId) for each id causes an N+1 query; replace it with a single batch query
that loads all Guests with an IN clause (use em.createQuery("SELECT g FROM Guest
g WHERE g.id IN :ids", Guest.class) or equivalent), pass guestIds as a
parameter, then map results by id and call booking.addGuest(...) for each id in
the original guestIds order (skipping missing entries) to preserve semantics;
ensure to reference the guestIds variable, em EntityManager and booking.addGuest
when making the change.
🧹 Nitpick comments (4)
src/main/java/org/example/App.java (2)

30-30: Consider using a database migration tool instead of hibernate.hbm2ddl.auto.

The update strategy can cause issues in production environments (irreversible changes, potential data loss). Consider using Flyway or Liquibase for controlled schema migrations.


58-58: Consider using a logging framework instead of System.out.println.

Production applications should use a logging framework (e.g., SLF4J with Logback) for better control over log levels, formatting, and output destinations.

src/main/java/org/example/entity/BookingService.java (2)

18-36: Consider adding pagination for list queries.

Loading all records without pagination could cause performance issues as data grows. For production use, consider adding offset/limit parameters.


69-69: Service layer should not write to System.out.

Service methods printing directly to console is not suitable for production. Consider using a logging framework or returning status messages to the caller.

Also applies to: 100-100, 112-112

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88194e2 and 988c6e2.

📒 Files selected for processing (2)
  • src/main/java/org/example/App.java
  • src/main/java/org/example/entity/BookingService.java
🔇 Additional comments (2)
src/main/java/org/example/App.java (2)

3-9: LGTM - Appropriate imports for JPA bootstrapping.

The imports support dynamic entity discovery, JPA operations, and the booking domain model.


48-60: Reasonable approach for initial data seeding.

Checking table count before seeding prevents duplicate data creation. The all-or-nothing assumption is acceptable for bootstrap scenarios.

Comment on lines +82 to +93
private static void hours(EntityManagerFactory emf) {
emf.runInTransaction(em -> {
String[] times = {"16:00", "16:30", "17:00", "17:30", "18:00", "18:30", "19:00"};
for (int i = 0; i < times.length; i++) {
String start = times[i];
String[] parts = start.split(":");
int hour = Integer.parseInt(parts[0]) + 2;
String end = hour + ":" + parts[1];
em.persist(new TimeSlot(start, end));
}
});
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Refactor time calculation to use LocalTime for correctness.

The current string-based time arithmetic is fragile and error-prone. For example, a start time of "23:00" would produce "25:00" as the end time, which is invalid.

⏰ Recommended fix using LocalTime
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
 private static void hours(EntityManagerFactory emf) {
     emf.runInTransaction(em -> {
         String[] times = {"16:00", "16:30", "17:00", "17:30", "18:00", "18:30", "19:00"};
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
         for (int i = 0; i < times.length; i++) {
             String start = times[i];
-            String[] parts = start.split(":");
-            int hour = Integer.parseInt(parts[0]) + 2;
-            String end = hour + ":" + parts[1];
+            LocalTime startTime = LocalTime.parse(start, formatter);
+            LocalTime endTime = startTime.plusHours(2);
+            String end = endTime.format(formatter);
             em.persist(new TimeSlot(start, end));
         }
     });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static void hours(EntityManagerFactory emf) {
emf.runInTransaction(em -> {
String[] times = {"16:00", "16:30", "17:00", "17:30", "18:00", "18:30", "19:00"};
for (int i = 0; i < times.length; i++) {
String start = times[i];
String[] parts = start.split(":");
int hour = Integer.parseInt(parts[0]) + 2;
String end = hour + ":" + parts[1];
em.persist(new TimeSlot(start, end));
}
});
}
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
private static void hours(EntityManagerFactory emf) {
emf.runInTransaction(em -> {
String[] times = {"16:00", "16:30", "17:00", "17:30", "18:00", "18:30", "19:00"};
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
for (int i = 0; i < times.length; i++) {
String start = times[i];
LocalTime startTime = LocalTime.parse(start, formatter);
LocalTime endTime = startTime.plusHours(2);
String end = endTime.format(formatter);
em.persist(new TimeSlot(start, end));
}
});
}
🤖 Prompt for AI Agents
In @src/main/java/org/example/App.java around lines 82 - 93, The hours method
currently computes end times by string math which breaks for wrap-around times;
update hours(EntityManagerFactory emf) to parse each start into
java.time.LocalTime (e.g., LocalTime.parse(start)), compute end by
startTime.plusHours(2) (or plusMinutes if needed), and use the LocalTime string
or formatted value when constructing the new TimeSlot before calling em.persist;
keep using the same times array and TimeSlot constructor but replace the manual
split/parse/concatenation logic with LocalTime-based arithmetic to handle day
rollovers correctly.

Comment on lines +141 to +183
private static void createBookingMenu(BookingService bookingService) {
System.out.println("\n═══ CREATE NEW BOOKING ═══");

// Visa tillgängliga bord
List<Table> tables = bookingService.getAllTables();
System.out.println("\n📋 Available Tables:");
tables.forEach(t -> System.out.println(" " + t.getId() + ". Table " + t.getTableNumber() + " (Capacity: " + t.getCapacity() + ")"));

Long tableId = Long.parseLong(IO.readln("\nEnter Table ID: "));

// Visa tillgängliga tider
List<TimeSlot> timeSlots = bookingService.getAllTimeSlots();
System.out.println("\n⏰ Available Time Slots:");
timeSlots.forEach(ts -> System.out.println(" " + ts.getId() + ". " + ts.getStartTime() + " - " + ts.getFinishTime()));

Long timeSlotId = Long.parseLong(IO.readln("\nEnter TimeSlot ID: "));

// Datum
String dateStr = IO.readln("\nEnter date (YYYY-MM-DD): ");
LocalDate date = LocalDate.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE);

// Antal gäster
int partySize = Integer.parseInt(IO.readln("\nEnter party size: "));

// Visa tillgängliga gäster
List<Guest> guests = bookingService.getAllGuests();
System.out.println("\n👥 Available Guests:");
guests.forEach(g -> System.out.println(" " + g.getId() + ". " + g.getName() + " (" + g.getContact() + ")"));

List<Long> guestIds = new ArrayList<>();
String addMore = "y";
while (addMore.equalsIgnoreCase("y")) {
Long guestId = Long.parseLong(IO.readln("\nEnter Guest ID: "));
guestIds.add(guestId);
addMore = IO.readln("Add another guest? (y/n): ");
}

try {
bookingService.createBooking(tableId, timeSlotId, date, partySize, guestIds);
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

MAJOR: No validation that party size fits table capacity.

The booking flow doesn't validate that the requested partySize is within the selected table's capacity. This allows overbooking beyond physical constraints.

✅ Recommended validation

After reading party size, add validation:

// After line 163
int partySize = Integer.parseInt(IO.readln("\nEnter party size: "));

// Add validation
Table selectedTable = tables.stream()
    .filter(t -> t.getId().equals(tableId))
    .findFirst()
    .orElse(null);

if (selectedTable != null && partySize > selectedTable.getCapacity()) {
    System.out.println("Error: Party size (" + partySize + ") exceeds table capacity (" + selectedTable.getCapacity() + ")");
    return;
}
🤖 Prompt for AI Agents
In @src/main/java/org/example/App.java around lines 141 - 183, The
createBookingMenu flow lacks validation that the entered partySize fits the
selected table's capacity; after parsing partySize, look up the Table from the
earlier "tables" list by matching t.getId() to tableId (e.g., Table
selectedTable = tables.stream().filter(t ->
t.getId().equals(tableId)).findFirst().orElse(null)), then if selectedTable !=
null && partySize > selectedTable.getCapacity() print a clear error like "Error:
Party size (...) exceeds table capacity (...)" and return early before calling
bookingService.createBooking; keep existing exception handling for
createBooking.

Comment on lines +13 to +15
public BookingService(EntityManagerFactory emf) {
this.emf = emf;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add null check for EntityManagerFactory parameter.

The constructor should validate that emf is not null to fail fast rather than allow NPE on first method invocation.

✅ Recommended validation
 public BookingService(EntityManagerFactory emf) {
+    if (emf == null) {
+        throw new IllegalArgumentException("EntityManagerFactory cannot be null");
+    }
     this.emf = emf;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public BookingService(EntityManagerFactory emf) {
this.emf = emf;
}
public BookingService(EntityManagerFactory emf) {
if (emf == null) {
throw new IllegalArgumentException("EntityManagerFactory cannot be null");
}
this.emf = emf;
}
🤖 Prompt for AI Agents
In @src/main/java/org/example/entity/BookingService.java around lines 13 - 15,
The BookingService constructor should validate its EntityManagerFactory
parameter to fail fast: in the BookingService(EntityManagerFactory emf)
constructor check that emf is not null and throw a clear unchecked exception
(e.g., IllegalArgumentException or NullPointerException) with a helpful message
if it is null, then assign it to the field; update any relevant tests if they
rely on null construction behavior.

Comment on lines +61 to +66
for (Long guestId : guestIds) {
Guest guest = em.find(Guest.class, guestId);
if (guest != null) {
booking.addGuest(guest);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

MAJOR: N+1 query problem when loading guests.

Loading guests one-by-one in a loop causes multiple database queries. Use a batch query instead for better performance.

⚡ Recommended fix
-        // Lägg till gäster
-        for (Long guestId : guestIds) {
-            Guest guest = em.find(Guest.class, guestId);
-            if (guest != null) {
-                booking.addGuest(guest);
-            }
-        }
+        // Lägg till gäster (batch query)
+        List<Guest> guests = em.createQuery(
+            "SELECT g FROM Guest g WHERE g.id IN :ids", Guest.class)
+            .setParameter("ids", guestIds)
+            .getResultList();
+        
+        if (guests.size() != guestIds.size()) {
+            throw new IllegalArgumentException("One or more guests not found");
+        }
+        
+        guests.forEach(booking::addGuest);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (Long guestId : guestIds) {
Guest guest = em.find(Guest.class, guestId);
if (guest != null) {
booking.addGuest(guest);
}
}
// Lägg till gäster (batch query)
List<Guest> guests = em.createQuery(
"SELECT g FROM Guest g WHERE g.id IN :ids", Guest.class)
.setParameter("ids", guestIds)
.getResultList();
if (guests.size() != guestIds.size()) {
throw new IllegalArgumentException("One or more guests not found");
}
guests.forEach(booking::addGuest);
🤖 Prompt for AI Agents
In @src/main/java/org/example/entity/BookingService.java around lines 61 - 66,
The loop in BookingService that calls em.find(Guest.class, guestId) for each id
causes an N+1 query; replace it with a single batch query that loads all Guests
with an IN clause (use em.createQuery("SELECT g FROM Guest g WHERE g.id IN
:ids", Guest.class) or equivalent), pass guestIds as a parameter, then map
results by id and call booking.addGuest(...) for each id in the original
guestIds order (skipping missing entries) to preserve semantics; ensure to
reference the guestIds variable, em EntityManager and booking.addGuest when
making the change.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @src/main/java/org/example/entity/Booking.java:
- Around line 21-23: The Booking.timeSlot relationship is missing a non-null
constraint; update the @JoinColumn on the timeSlot field in class Booking to
include nullable = false (i.e., @JoinColumn(name="timeslot_id", nullable =
false)) so the TimeSlot association is required, matching the table relationship
constraint.

In @src/main/java/org/example/entity/BookingService.java:
- Around line 66-77: The JPQL in BookingService that computes existingBookings
uses the string literal 'CANCELLED' for b.status, which is fragile; change the
query to compare b.status to a named parameter (e.g., :status) and bind that
parameter to the enum constant (BookingStatus.CANCELLED) when calling
setParameter on the Query used to create existingBookings; apply the same
pattern to the other JPQL in getAvailableTables (the query referenced around
lines 199-214) so both queries use enum parameter binding instead of hard-coded
strings.
- Around line 167-172: The switch on newStatus currently handles CONFIRMED,
CANCELLED, COMPLETED and NO_SHOW but omits PENDING, so add a PENDING branch
(e.g., case PENDING -> booking.setStatus(BookingStatus.PENDING) or case PENDING
-> booking.revertToPending()/booking.resetToPending() depending on your Booking
API) to explicitly handle resetting a booking to pending; alternatively add a
default branch that logs/throws (e.g., process a warning or throw
IllegalArgumentException) to avoid silently ignoring unexpected statuses —
update the switch in updateBookingStatus to include either the PENDING case or a
default handler that surfaces the issue.
🧹 Nitpick comments (8)
src/main/java/org/example/App.java (3)

105-139: The emf parameter in mainMenu is unused.

The method signature accepts EntityManagerFactory emf but never uses it. Either remove the parameter or document why it's needed for future use.

Remove unused parameter
-public static void mainMenu(BookingService bookingService, EntityManagerFactory emf) {
+public static void mainMenu(BookingService bookingService) {

150-157: User input parsing lacks graceful handling for invalid numeric input.

Long.parseLong() and Integer.parseInt() on lines 150, 157, 184, and 203 will throw NumberFormatException if the user enters non-numeric input. While there's a catch block at line 235, intermediate operations may leave the application in an inconsistent state.

Consider validating input before parsing or wrapping individual parse operations:

Add input validation helper
private static Long parseLongSafe(String input, String fieldName) {
    try {
        return Long.parseLong(input.trim());
    } catch (NumberFormatException e) {
        throw new IllegalArgumentException("Invalid " + fieldName + ": must be a number");
    }
}

Also applies to: 184-184, 203-203


159-181: Date validation is duplicated between App and BookingService.

The date validation logic (checking past dates and 3-month limit) exists both here (lines 167-177) and in BookingService.createBooking() (lines 54-63). This duplication could lead to inconsistencies if one is updated without the other.

Consider centralizing validation in the service layer only, or extracting to a shared utility.

src/main/java/org/example/entity/Booking.java (3)

43-52: Constructor accepting id parameter is problematic for new entities.

For JPA entities with @GeneratedValue, the id should not be set manually via constructor. This constructor suggests client code might pass an id, which would be ignored or cause issues when persisting new entities.

Consider removing the id parameter or creating a separate constructor for entity creation:

Proposed fix
-public Booking(Long id, LocalDate date, TimeSlot timeSlot, int partySize, Table table, List<Guest> guests) {
-    this.id = id;
+public Booking(LocalDate date, TimeSlot timeSlot, int partySize, Table table, List<Guest> guests) {
     this.date = date;
     this.timeSlot = timeSlot;
     this.partySize = partySize;
     this.table = table;
     this.guests = guests;
     this.status = BookingStatus.PENDING;
 }

97-103: Inconsistent getter/setter naming for partySize field.

The field is named partySize but accessors are getParty()/setParty(). This breaks JavaBean conventions and could confuse other developers or tools expecting getPartySize()/setPartySize().

Rename to follow conventions
-public int getParty() {
+public int getPartySize() {
     return partySize;
 }

-public void setParty(int partySize) {
+public void setPartySize(int partySize) {
     this.partySize = partySize;
 }

67-78: Status transition methods lack state validation.

Methods like confirmBooking(), cancelBooking(), etc., don't validate the current state before transitioning. For example, completing a cancelled booking or confirming an already completed booking might not be valid business logic.

Consider adding state validation:

Example with state validation
public void confirmBooking() {
    if (this.status != BookingStatus.PENDING) {
        throw new IllegalStateException("Can only confirm PENDING bookings");
    }
    this.status = BookingStatus.CONFIRMED;
}
src/main/java/org/example/entity/BookingService.java (2)

144-157: getBooking() throws NoResultException if booking not found.

Using getSingleResult() throws jakarta.persistence.NoResultException when no matching entity exists. Unlike deleteBooking() which handles this gracefully, callers of getBooking() must catch this exception.

Consider using getResultStream().findFirst() or documenting the exception:

Proposed fix returning Optional
-public Booking getBooking(Long id) {
+public Optional<Booking> getBooking(Long id) {
     return emf.callInTransaction(em ->
         em.createQuery(
                 "SELECT b FROM Booking b " +
                     "LEFT JOIN FETCH b.guests " +
                     "LEFT JOIN FETCH b.table " +
                     "LEFT JOIN FETCH b.timeSlot " +
                     "WHERE b.id = :id",
                 Booking.class
             )
             .setParameter("id", id)
-            .getSingleResult()
+            .getResultStream()
+            .findFirst()
     );
 }

110-110: Service layer uses System.out.println for user feedback.

Mixing console output with business logic violates separation of concerns. The service layer should focus on data operations and throw exceptions or return results; the UI layer (App.java) should handle user messaging.

Consider returning result objects or throwing exceptions that the caller can translate into user messages.

Also applies to: 163-163, 174-174, 191-191, 194-194

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 988c6e2 and 5a39fed.

📒 Files selected for processing (3)
  • src/main/java/org/example/App.java
  • src/main/java/org/example/entity/Booking.java
  • src/main/java/org/example/entity/BookingService.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/org/example/entity/Booking.java (3)
src/main/java/org/example/entity/TimeSlot.java (1)
  • Entity (5-59)
src/main/java/org/example/entity/Guest.java (1)
  • Entity (9-78)
src/main/java/org/example/entity/Table.java (1)
  • Entity (10-90)
🔇 Additional comments (6)
src/main/java/org/example/App.java (2)

82-93: End time calculation produces invalid times for slots after 22:00.

The logic int hour = Integer.parseInt(parts[0]) + 2 adds 2 hours to the start time. For "19:00", this yields "21:00", but if times extended further (e.g., "23:00"), this would produce "25:00" which is invalid. Additionally, the slot duration appears inconsistent—some restaurants use 30-minute or 1-hour slots, but here slots are 2 hours apart despite 30-minute increments in times.

Verify this is the intended behavior. If slots should represent 2-hour reservations, consider using LocalTime for proper time arithmetic:

Suggested improvement using LocalTime
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
 private static void hours(EntityManagerFactory emf) {
     emf.runInTransaction(em -> {
         String[] times = {"16:00", "16:30", "17:00", "17:30", "18:00", "18:30", "19:00"};
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
         for (int i = 0; i < times.length; i++) {
             String start = times[i];
-            String[] parts = start.split(":");
-            int hour = Integer.parseInt(parts[0]) + 2;
-            String end = hour + ":" + parts[1];
+            LocalTime startTime = LocalTime.parse(start, formatter);
+            LocalTime endTime = startTime.plusHours(2);
+            String end = endTime.format(formatter);
             em.persist(new TimeSlot(start, end));
         }
     });
 }

36-45: LGTM on resource management.

Good use of try-with-resources for the EntityManagerFactory, ensuring proper cleanup on exit.

src/main/java/org/example/entity/Booking.java (2)

129-139: toString() may trigger LazyInitializationException outside transaction.

Including timeSlot, table, and guests in toString() can cause lazy loading issues when called outside an active persistence context. Consider either:

  1. Using only IDs in toString()
  2. Making relationships eagerly fetched (not recommended)
  3. Using a dedicated DTO for display

36-41: ManyToMany cascade configuration looks appropriate.

Using CascadeType.PERSIST and CascadeType.MERGE is correct for this relationship—allowing guests to be persisted/merged with the booking without cascading deletes.

src/main/java/org/example/entity/BookingService.java (2)

28-112: Comprehensive validation in createBooking is well implemented.

The method properly validates table existence, time slot existence, capacity constraints, party size bounds, date range, existing bookings conflict, and guest existence. Good use of transactional context.


132-142: Good use of JOIN FETCH to avoid N+1 queries.

Eager fetching of guests, table, and timeSlot in getAllBookings() prevents lazy loading issues when accessing these relationships outside the transaction.

Comment on lines +66 to +77
Long existingBookings = em.createQuery(
"SELECT COUNT(b) FROM Booking b " +
"WHERE b.table.id = :tableId " +
"AND b.date = :date " +
"AND b.timeSlot.id = :timeSlotId " +
"AND b.status != 'CANCELLED'",
Long.class
)
.setParameter("tableId", tableId)
.setParameter("date", date)
.setParameter("timeSlotId", timeSlotId)
.getSingleResult();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Using string literal 'CANCELLED' instead of enum parameter in JPQL.

Comparing b.status with a string literal 'CANCELLED' works because of @Enumerated(EnumType.STRING), but it's fragile and bypasses type safety. If the enum value is renamed, this query silently breaks.

Use a parameter binding instead:

Proposed fix for createBooking query (lines 66-77)
 Long existingBookings = em.createQuery(
         "SELECT COUNT(b) FROM Booking b " +
             "WHERE b.table.id = :tableId " +
             "AND b.date = :date " +
             "AND b.timeSlot.id = :timeSlotId " +
-            "AND b.status != 'CANCELLED'",
+            "AND b.status != :cancelledStatus",
         Long.class
     )
     .setParameter("tableId", tableId)
     .setParameter("date", date)
     .setParameter("timeSlotId", timeSlotId)
+    .setParameter("cancelledStatus", BookingStatus.CANCELLED)
     .getSingleResult();
Proposed fix for getAvailableTables query (lines 199-214)
 return emf.callInTransaction(em ->
     em.createQuery(
             "SELECT t FROM Table t WHERE t.id NOT IN " +
                 "(SELECT b.table.id FROM Booking b " +
                 "WHERE b.date = :date " +
                 "AND b.timeSlot.id = :timeSlotId " +
-                "AND b.status != 'CANCELLED')",
+                "AND b.status != :cancelledStatus)",
             Table.class
         )
         .setParameter("date", date)
         .setParameter("timeSlotId", timeSlotId)
+        .setParameter("cancelledStatus", BookingStatus.CANCELLED)
         .getResultList()
 );

Also applies to: 199-214

🤖 Prompt for AI Agents
In @src/main/java/org/example/entity/BookingService.java around lines 66 - 77,
The JPQL in BookingService that computes existingBookings uses the string
literal 'CANCELLED' for b.status, which is fragile; change the query to compare
b.status to a named parameter (e.g., :status) and bind that parameter to the
enum constant (BookingStatus.CANCELLED) when calling setParameter on the Query
used to create existingBookings; apply the same pattern to the other JPQL in
getAvailableTables (the query referenced around lines 199-214) so both queries
use enum parameter binding instead of hard-coded strings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @src/main/java/org/example/App.java:
- Around line 21-34: The code in the PersistenceConfiguration setup (the
HibernatePersistenceConfiguration instance creating jdbcUrl, jdbcUsername,
jdbcPassword and the hibernate.hbm2ddl.auto property) hardcodes sensitive DB
credentials and enables automatic schema updates; change it to read JDBC url,
username and password from environment variables or an external config provider
(e.g., System.getenv or a injected Config service) and validate/fail-fast if
missing, replace the literal jdbcUrl/jdbcUsername/jdbcPassword calls on the
PersistenceConfiguration builder with those values, and remove or conditionally
set "hibernate.hbm2ddl.auto" (do not use "update" in production) — instead
document/enable using a migration tool like Flyway/Liquibase or set the property
only for non-production environments based on an ENV flag; locate these changes
by editing the HibernatePersistenceConfiguration construction in App.java or
wherever PersistenceConfiguration is built.
- Around line 93-127: The mainMenu method currently accepts an unused
EntityManagerFactory parameter (EntityManagerFactory emf); remove the unused
parameter from the mainMenu signature (change public static void
mainMenu(BookingService bookingService, EntityManagerFactory emf) to public
static void mainMenu(BookingService bookingService)) and update all call sites
(e.g., the invocation in main()) to stop passing emf; search for other
references to mainMenu that pass emf and adjust them accordingly to eliminate
the unused coupling.
- Around line 48-59: createInitialData checks the count of Table entities but
never creates any Table rows; add logic to create and persist initial Table
entities when count == 0 (either implement a new
createTables(EntityManagerFactory emf) function or extend hours/createGuest to
persist Table instances). Ensure you persist several Table entities inside a
transaction (e.g., using emf.callInTransaction or the existing persistence
pattern), assign any required fields on the Table entity, and call that creation
function from createInitialData alongside hours(emf) and createGuest(emf).
🧹 Nitpick comments (2)
src/main/java/org/example/App.java (2)

70-81: Consider a more descriptive method name.

The method name hours doesn't clearly convey its purpose. A name like createTimeSlots or initializeTimeSlots would be more self-documenting.

♻️ Suggested rename
-private static void hours(EntityManagerFactory emf) {
+private static void createTimeSlots(EntityManagerFactory emf) {

166-168: Catch specific exception type instead of generic Exception.

Catching DateTimeParseException would be more precise and avoid accidentally swallowing unexpected errors.

♻️ Suggested fix
-            } catch (Exception e) {
+            } catch (java.time.format.DateTimeParseException e) {
                 System.out.println("Invalid date format! Please use YYYY-MM-DD");
             }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a39fed and a8a2cc7.

📒 Files selected for processing (1)
  • src/main/java/org/example/App.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/org/example/App.java (1)
src/main/java/org/example/entity/BookingService.java (1)
  • BookingService (9-215)
🔇 Additional comments (7)
src/main/java/org/example/App.java (7)

3-14: LGTM!

Imports are appropriate. The explicit org.example.entity.Table import correctly disambiguates from java.util.Table.


61-68: LGTM!

Seed data creation is straightforward and appropriate for development/demo purposes.


83-91: LGTM!

Good use of ClassGraph for dynamic entity discovery with proper resource management via try-with-resources.


230-279: LGTM!

The update menu properly displays available bookings, handles invalid input, and delegates status updates to the service layer.


283-307: LGTM!

Good practice with the confirmation prompt before deletion and proper empty-state handling.


309-332: LGTM!

The overloaded showBookings methods reduce code duplication. Since BookingService.getAllBookings() uses JOIN FETCH for related entities, the access to timeSlot and table properties is safe.


334-346: LGTM!

Simple and clean display methods that properly delegate to the service layer.

Comment on lines +48 to +59
private static void createInitialData(EntityManagerFactory emf) {
// Kolla om data redan finns
Long count = emf.callInTransaction(em ->
em.createQuery("SELECT COUNT(t) FROM Table t", Long.class).getSingleResult()
);

if (count == 0) {
hours(emf);
createGuest(emf);
System.out.println("Initial data created!");
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing table creation in initial data setup.

The method checks for Table count to determine if initialization is needed, but neither hours() nor createGuest() creates any Table entities. The booking system will fail as users won't have any tables to book.

🐛 Proposed fix to add table creation
 private static void createInitialData(EntityManagerFactory emf) {
     // Kolla om data redan finns
     Long count = emf.callInTransaction(em ->
         em.createQuery("SELECT COUNT(t) FROM Table t", Long.class).getSingleResult()
     );

     if (count == 0) {
+        createTables(emf);
         hours(emf);
         createGuest(emf);
         System.out.println("Initial data created!");
     }
 }

+private static void createTables(EntityManagerFactory emf) {
+    emf.runInTransaction(em -> {
+        em.persist(new Table(1, 2));  // Table 1, capacity 2
+        em.persist(new Table(2, 4));  // Table 2, capacity 4
+        em.persist(new Table(3, 6));  // Table 3, capacity 6
+        em.persist(new Table(4, 8));  // Table 4, capacity 8
+    });
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static void createInitialData(EntityManagerFactory emf) {
// Kolla om data redan finns
Long count = emf.callInTransaction(em ->
em.createQuery("SELECT COUNT(t) FROM Table t", Long.class).getSingleResult()
);
if (count == 0) {
hours(emf);
createGuest(emf);
System.out.println("Initial data created!");
}
}
private static void createInitialData(EntityManagerFactory emf) {
// Kolla om data redan finns
Long count = emf.callInTransaction(em ->
em.createQuery("SELECT COUNT(t) FROM Table t", Long.class).getSingleResult()
);
if (count == 0) {
createTables(emf);
hours(emf);
createGuest(emf);
System.out.println("Initial data created!");
}
}
private static void createTables(EntityManagerFactory emf) {
emf.runInTransaction(em -> {
em.persist(new Table(1, 2)); // Table 1, capacity 2
em.persist(new Table(2, 4)); // Table 2, capacity 4
em.persist(new Table(3, 6)); // Table 3, capacity 6
em.persist(new Table(4, 8)); // Table 4, capacity 8
});
}
🤖 Prompt for AI Agents
In @src/main/java/org/example/App.java around lines 48 - 59, createInitialData
checks the count of Table entities but never creates any Table rows; add logic
to create and persist initial Table entities when count == 0 (either implement a
new createTables(EntityManagerFactory emf) function or extend hours/createGuest
to persist Table instances). Ensure you persist several Table entities inside a
transaction (e.g., using emf.callInTransaction or the existing persistence
pattern), assign any required fields on the Table entity, and call that creation
function from createInitialData alongside hours(emf) and createGuest(emf).

Comment on lines +93 to +127
public static void mainMenu(BookingService bookingService, EntityManagerFactory emf) {
boolean running = true;
while (running) {
String menu = """

╔════════════════════════════════════╗
║ RESTAURANT BOOKING SYSTEM ║
╠════════════════════════════════════╣
║ 1. CREATE BOOKING ║
║ 2. UPDATE BOOKING ║
║ 3. VIEW ALL BOOKINGS ║
║ 4. DELETE BOOKING ║
║ 5. VIEW TABLES ║
║ 6. VIEW GUESTS ║
║ 7. EXIT ║
╚════════════════════════════════════╝
""";

String select = IO.readln(menu + "\nSelect option: ").toLowerCase();

switch (select) {
case "create booking", "cb", "1" -> createBookingMenu(bookingService);
case "update booking", "ub", "2" -> updateBookingMenu(bookingService);
case "view all bookings", "rb", "3" -> showBookings(bookingService);
case "delete booking", "db", "4" -> deleteBookingMenu(bookingService);
case "view tables", "5" -> viewTables(bookingService);
case "view guests", "6" -> viewGuests(bookingService);
case "exit", "7" -> {
System.out.println("Goodbye!");
running = false;
}
default -> System.out.println("Invalid option!");
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove unused emf parameter.

The EntityManagerFactory emf parameter is passed to the method but never used. This creates unnecessary coupling and confusion.

♻️ Proposed fix
-public static void mainMenu(BookingService bookingService, EntityManagerFactory emf) {
+public static void mainMenu(BookingService bookingService) {

Also update the call site in main():

-mainMenu(bookingService, emf);
+mainMenu(bookingService);
🤖 Prompt for AI Agents
In @src/main/java/org/example/App.java around lines 93 - 127, The mainMenu
method currently accepts an unused EntityManagerFactory parameter
(EntityManagerFactory emf); remove the unused parameter from the mainMenu
signature (change public static void mainMenu(BookingService bookingService,
EntityManagerFactory emf) to public static void mainMenu(BookingService
bookingService)) and update all call sites (e.g., the invocation in main()) to
stop passing emf; search for other references to mainMenu that pass emf and
adjust them accordingly to eliminate the unused coupling.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @src/main/java/org/example/App.java:
- Around line 154-157: The inline comment above maxDate is truncated ("// hur
många månader fram man kan boka(kan ä"); either complete it with a full Swedish
sentence describing that maxDate is today.plusMonths(3) (e.g., "// hur många
månader fram man kan boka (kan ändras efter behov)") or remove the incomplete
fragment; update the comment near LocalDate today and LocalDate maxDate to be a
clear, complete sentence or delete it so no truncated text remains.
- Around line 21-34: The code in the PersistenceConfiguration construction
hardcodes DB credentials; replace the literal "root"/"root123" with values read
from environment/config at runtime: fetch variables (e.g.,
System.getenv("DB_USERNAME") and System.getenv("DB_PASSWORD") or from a config
provider), validate defaults/fallbacks, and pass those variables into the
HibernatePersistenceConfiguration builder used to create cfg (the
PersistenceConfiguration/HibernatePersistenceConfiguration call site) so
credentials are not embedded in source; ensure any new config keys are
documented and sensitive files are excluded from VCS.

In @src/main/java/org/example/entity/BookingService.java:
- Line 1: The package declaration in BookingService.java does not match its
directory; update the top of the BookingService source to declare "package
org.example.entity;" so the BookingService class's package aligns with its file
location (alternatively, move the file into the org.example.service directory),
but the simplest fix is to change the package declaration in the BookingService
class to org.example.entity.
- Around line 152-157: The switch in updateBookingStatus handles
CONFIRMED/CANCELLED/COMPLETED/NO_SHOW but omits PENDING, so add a PENDING branch
to explicitly handle or reject it; update the switch on newStatus (the pattern
matching switch in updateBookingStatus) to include either `case PENDING ->
booking.resetToPending();` (or `booking.setStatus(BookingStatus.PENDING);` if
that setter exists) or `case PENDING -> throw new
IllegalArgumentException("Cannot reset booking to PENDING status");` depending
on whether resetting is allowed.
🧹 Nitpick comments (7)
src/main/java/org/example/App.java (2)

172-172: Missing validation for party size input.

If the user enters a non-positive number or zero, parsing succeeds but the value might be invalid. While BookingService.createBooking validates partySize < 1, providing immediate feedback at input time would improve UX.

💡 Suggested validation
             // Antal gäster
-            int partySize = Integer.parseInt(IO.readln("\nEnter party size: "));
+            int partySize = Integer.parseInt(IO.readln("\nEnter party size: "));
+            if (partySize < 1) {
+                System.out.println("Party size must be at least 1!");
+                return;
+            }

322-332: Potential NullPointerException when displaying bookings.

If b.getTimeSlot() or b.getTable() returns null (which shouldn't happen with proper data, but could occur with inconsistent DB state), accessing their methods will throw NPE.

🛡️ Defensive null checks
     private static void showBookings(List<Booking> bookings) {
         bookings.forEach(b -> {
             System.out.println("\n📅 Booking ID: " + b.getId());
             System.out.println("   Date: " + b.getDate());
-            System.out.println("   Time: " + b.getTimeSlot().getStartTime() + " - " + b.getTimeSlot().getFinishTime());
-            System.out.println("   Table: " + b.getTable().getTableNumber());
+            System.out.println("   Time: " + (b.getTimeSlot() != null 
+                ? b.getTimeSlot().getStartTime() + " - " + b.getTimeSlot().getFinishTime() 
+                : "N/A"));
+            System.out.println("   Table: " + (b.getTable() != null 
+                ? b.getTable().getTableNumber() 
+                : "N/A"));
             System.out.println("   Party Size: " + b.getParty());
             System.out.println("   Status: " + b.getStatus());
             System.out.println("   Guests: " + b.getGuests().stream().map(Guest::getName).toList());
         });
     }
src/main/java/org/example/entity/Booking.java (3)

21-23: Missing nullable = false on timeSlot join column.

The table association at line 33 specifies nullable = false, but timeSlot does not. If a booking always requires a time slot, add the constraint for consistency and data integrity.

✏️ Suggested fix
     @ManyToOne
-    @JoinColumn(name="timeslot_id")
+    @JoinColumn(name="timeslot_id", nullable = false)
     private TimeSlot timeSlot; //time selected from the determited times

86-92: Inconsistent naming: getParty() / setParty() for field partySize.

The getter/setter names don't follow the field name convention. This could cause confusion and may break frameworks that rely on JavaBeans naming conventions.

✏️ Suggested fix
-    public int getParty() {
+    public int getPartySize() {
         return partySize;
     }

-    public void setParty(int partySize) {
+    public void setPartySize(int partySize) {
         this.partySize = partySize;
     }

Note: This requires updating callers in App.java and BookingService.java.


56-67: Status transition methods allow invalid state transitions.

The status methods (confirmBooking, cancelBooking, etc.) unconditionally set the status without validating the current state. For example, a COMPLETED booking could be changed to CONFIRMED, which may not be valid business logic.

💡 Example with state validation
public void confirmBooking() {
    if (this.status != BookingStatus.PENDING) {
        throw new IllegalStateException("Can only confirm PENDING bookings");
    }
    this.status = BookingStatus.CONFIRMED;
}
src/main/java/org/example/entity/BookingService.java (2)

65-77: JPQL compares enum field to string literal.

The query uses b.status != 'CANCELLED' which compares an @Enumerated(EnumType.STRING) field against a string literal. While this works, it's fragile—renaming the enum constant would silently break the query. Use a parameter instead.

✏️ Suggested fix using parameter
             Long existingBookings = em.createQuery(
                     "SELECT COUNT(b) FROM Booking b " +
                         "WHERE b.table.id = :tableId " +
                         "AND b.date = :date " +
                         "AND b.timeSlot.id = :timeSlotId " +
-                        "AND b.status != 'CANCELLED'",
+                        "AND b.status != :cancelledStatus",
                     Long.class
                 )
                 .setParameter("tableId", tableId)
                 .setParameter("date", date)
                 .setParameter("timeSlotId", timeSlotId)
+                .setParameter("cancelledStatus", BookingStatus.CANCELLED)
                 .getSingleResult();

144-161: Inconsistent error handling: uses System.out.println instead of throwing exception.

At line 148, when a booking is not found, the method prints a message and returns silently. This is inconsistent with createBooking which throws IllegalArgumentException. The caller has no way to know the operation failed.

✏️ Suggested fix for consistent error handling
     public void updateBookingStatus(Long bookingId, BookingStatus newStatus) {
         emf.runInTransaction(em -> {
             Booking booking = em.find(Booking.class, bookingId);
             if (booking == null) {
-                System.out.println("Booking with ID " + bookingId + " not found!");
-                return;
+                throw new IllegalArgumentException("Booking with ID " + bookingId + " not found!");
             }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8a2cc7 and 3e6d5bb.

📒 Files selected for processing (4)
  • src/main/java/org/example/App.java
  • src/main/java/org/example/entity/Booking.java
  • src/main/java/org/example/entity/BookingService.java
  • src/main/java/org/example/entity/Table.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/example/entity/Table.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/org/example/App.java (1)
src/main/java/org/example/entity/BookingService.java (1)
  • BookingService (9-183)
src/main/java/org/example/entity/Booking.java (3)
src/main/java/org/example/entity/Table.java (1)
  • Entity (10-69)
src/main/java/org/example/entity/TimeSlot.java (1)
  • Entity (5-59)
src/main/java/org/example/entity/Guest.java (1)
  • Entity (9-78)
🔇 Additional comments (6)
src/main/java/org/example/App.java (2)

71-81: Time slot end time calculation assumes 2-hour slots but doesn't handle edge cases.

The calculation hour + 2 at line 76 will produce incorrect results for times like "19:00" which would yield "21:00" as the end time. This is fine if intentional, but the last slot (19:00) would have an end time of 21:00, while other slots would overlap (e.g., 16:00-18:00 overlaps with 17:00-19:00).

Consider using LocalTime parsing for safer time arithmetic and clarify whether overlapping slots are intentional.


93-127: LGTM!

The main menu implementation is clean with a well-formatted UI and intuitive input handling supporting multiple input formats (number, abbreviation, full text).

src/main/java/org/example/entity/Booking.java (2)

9-16: LGTM!

The entity mapping with @GeneratedValue(strategy = GenerationType.IDENTITY) is appropriate for MySQL auto-increment, and the table naming is explicit.


36-51: LGTM!

The bidirectional relationship management in addGuest and removeGuest properly synchronizes both sides of the ManyToMany relationship, which is essential for JPA consistency.

src/main/java/org/example/entity/BookingService.java (2)

28-112: LGTM!

The createBooking method has comprehensive validation covering table existence, time slot existence, capacity limits, date range, double-booking prevention, and guest requirements. The transactional boundary is properly defined.


132-142: LGTM!

The getAllBookings query correctly uses DISTINCT with multiple LEFT JOIN FETCH clauses to eagerly load associations and avoid N+1 query problems.

Comment on lines +154 to +157
// Validera att datumet är korrekt
LocalDate today = LocalDate.now();
// hur många månader fram man kan boka(kan ä
LocalDate maxDate = today.plusMonths(3);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incomplete comment on line 156.

The comment // hur många månader fram man kan boka(kan ä appears to be truncated mid-sentence. This should be completed or removed.

✏️ Suggested fix
-                    // hur många månader fram man kan boka(kan ä
+                    // Maximum booking window: 3 months in advance
                     LocalDate maxDate = today.plusMonths(3);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Validera att datumet är korrekt
LocalDate today = LocalDate.now();
// hur många månader fram man kan boka(kan ä
LocalDate maxDate = today.plusMonths(3);
// Validera att datumet är korrekt
LocalDate today = LocalDate.now();
// Maximum booking window: 3 months in advance
LocalDate maxDate = today.plusMonths(3);
🤖 Prompt for AI Agents
In @src/main/java/org/example/App.java around lines 154 - 157, The inline
comment above maxDate is truncated ("// hur många månader fram man kan boka(kan
ä"); either complete it with a full Swedish sentence describing that maxDate is
today.plusMonths(3) (e.g., "// hur många månader fram man kan boka (kan ändras
efter behov)") or remove the incomplete fragment; update the comment near
LocalDate today and LocalDate maxDate to be a clear, complete sentence or delete
it so no truncated text remains.

@@ -0,0 +1,183 @@
package org.example.service;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Package declaration does not match file location.

The file is located at src/main/java/org/example/entity/BookingService.java but declares package org.example.service;. This will cause compilation errors.

Either move the file to src/main/java/org/example/service/BookingService.java or change the package declaration to org.example.entity.

🤖 Prompt for AI Agents
In @src/main/java/org/example/entity/BookingService.java at line 1, The package
declaration in BookingService.java does not match its directory; update the top
of the BookingService source to declare "package org.example.entity;" so the
BookingService class's package aligns with its file location (alternatively,
move the file into the org.example.service directory), but the simplest fix is
to change the package declaration in the BookingService class to
org.example.entity.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/main/java/org/example/App.java`:
- Around line 322-332: In showBookings, guard against potential NPEs by
null-checking booking associations before dereferencing: for each Booking b (in
showBookings), check b.getTimeSlot() and b.getTable() (and b.getGuests()) and
print safe defaults like "N/A" or an empty list when null; use conditional
expressions to obtain start/finish times only if getTimeSlot() != null and table
number only if getTable() != null, and handle b.getGuests() == null by printing
an empty list or "No guests" to avoid calling stream() on null.
- Around line 21-34: The JDBC credential env vars are mismatched with
docker-compose: the code calls
PersistenceConfiguration.jdbcUsername(System.getenv("MYSQL_USER")) and
jdbcPassword(System.getenv("MYSQL_ROOT_PASSWORD")) which can produce nulls;
update App.java to use consistent env vars and safe fallbacks: set jdbcUsername
to System.getenv("MYSQL_USER") != null ? System.getenv("MYSQL_USER") : "root"
(or another explicit default) and jdbcPassword to
System.getenv("MYSQL_PASSWORD") != null ? System.getenv("MYSQL_PASSWORD") :
System.getenv("MYSQL_ROOT_PASSWORD") (or a default), or alternatively change to
use a single matching pair (e.g., MYSQL_ROOT_PASSWORD and hardcoded "root"
username) and document adding MYSQL_USER/MYSQL_PASSWORD to docker-compose;
ensure the PersistenceConfiguration chain (jdbcUsername, jdbcPassword) never
receives null values.
♻️ Duplicate comments (9)
docker-compose.yml (2)

10-11: Consider restricting port binding to localhost.

The port 3306:3306 binds to all network interfaces, which may expose the database externally. For local development, binding to localhost is safer.


7-9: Environment variables lack default values - application may fail to start.

The environment variables ${MYSQL_ROOT_PASSWORD} and ${MYSQL_DATABASE} will be empty strings if not set, which may cause MySQL container to fail or behave unexpectedly. Consider adding default values:

🔧 Proposed fix with defaults
     environment:
-      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
-      MYSQL_DATABASE: ${MYSQL_DATABASE}
+      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root123}
+      MYSQL_DATABASE: ${MYSQL_DATABASE:-restaurant_booking}

Alternatively, create a .env.example file documenting required variables.

src/main/java/org/example/App.java (5)

48-60: Missing table creation in initial data setup.

The method checks TimeSlot count but never creates any Table entities. Users won't have tables to book against. The message on line 58 asks users to create tables manually, which is a poor user experience.


71-81: Refactor time calculation to use LocalTime for correctness.

The string-based time arithmetic is fragile. For example, "23:00" would produce "25:00" as the end time.


93-127: Remove unused emf parameter from mainMenu.

The EntityManagerFactory emf parameter is passed but never used, creating unnecessary coupling.


154-157: Incomplete comment on line 156.

The comment // hur många månader fram man kan boka(kan ä is truncated mid-sentence.


129-228: No validation that party size fits table capacity in UI layer.

While BookingService.createBooking() validates capacity, providing early feedback in the UI before calling the service would improve user experience.

src/main/java/org/example/entity/service/BookingService.java (2)

13-15: Add null check for EntityManagerFactory parameter.

The constructor should validate that emf is not null to fail fast.


100-108: N+1 query problem when loading guests.

Loading guests one-by-one in a loop causes multiple database queries. Consider using a batch query for better performance.

🧹 Nitpick comments (2)
src/main/java/org/example/entity/Booking.java (1)

43-51: Consider null check in addGuest/removeGuest.

If a null guest is passed, guest.getBookings() will throw NPE.

♻️ Defensive null check
     public void addGuest(Guest guest){
+        if (guest == null) return;
         guests.add(guest);
         guest.getBookings().add(this);
     }

     public void removeGuest(Guest guest){
+        if (guest == null) return;
         guests.remove(guest);
         guest.getBookings().remove(this);
     }
src/main/java/org/example/entity/service/BookingService.java (1)

165-184: Consider returning a boolean or throwing for not-found scenarios.

The deleteBooking method silently prints a message when the booking isn't found. The caller has no way to know if the deletion succeeded. Consider returning a boolean or throwing an exception for better programmatic handling.

♻️ Return success indicator
-    public void deleteBooking(Long bookingId) {
+    public boolean deleteBooking(Long bookingId) {
         emf.runInTransaction(em -> {
             try {
                 Booking booking = em.createQuery(
                         "SELECT b FROM Booking b " +
                             "LEFT JOIN FETCH b.guests " +
                             "WHERE b.id = :id",
                         Booking.class
                     )
                     .setParameter("id", bookingId)
                     .getSingleResult();

                 em.remove(booking);
                 System.out.println("Booking deleted successfully!");
+                return true;

             } catch (jakarta.persistence.NoResultException e) {
                 System.out.println("Booking with ID " + bookingId + " not found!");
+                return false;
             }
         });
     }

Note: This would require changing runInTransaction to callInTransaction to return the value.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e6d5bb and f12317c.

📒 Files selected for processing (6)
  • .gitignore
  • docker-compose.yml
  • src/main/java/org/example/App.java
  • src/main/java/org/example/entity/Booking.java
  • src/main/java/org/example/entity/service/BookingService.java
  • src/main/resources/META-INF/persistence.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • .gitignore
  • src/main/resources/META-INF/persistence.xml
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/org/example/App.java (1)
src/main/java/org/example/entity/service/BookingService.java (1)
  • BookingService (9-185)
src/main/java/org/example/entity/Booking.java (3)
src/main/java/org/example/entity/TimeSlot.java (1)
  • Entity (5-59)
src/main/java/org/example/entity/Guest.java (1)
  • Entity (9-78)
src/main/java/org/example/entity/Table.java (1)
  • Entity (10-69)
🔇 Additional comments (5)
src/main/java/org/example/App.java (1)

309-320: LGTM - Good use of method overloading for showBookings.

The two showBookings methods provide clean separation between fetching bookings and displaying them, enabling reuse in updateBookingMenu and deleteBookingMenu.

src/main/java/org/example/entity/Booking.java (2)

9-41: LGTM - Entity mapping is well-structured.

The JPA annotations are correctly configured with appropriate constraints (nullable = false), cascade types for the guest relationship, and proper bidirectional association management.


119-127: LGTM - toString() no longer causes circular reference issues.

The simplified toString() now only includes scalar fields, avoiding the previous StackOverflowError from circular references with table and guests.

src/main/java/org/example/entity/service/BookingService.java (2)

27-113: LGTM - Comprehensive validation in createBooking.

The method now includes proper validation for:

  • Table and TimeSlot existence
  • Party size bounds and capacity constraints
  • Date range validation (not in past, within 3 months)
  • Double-booking prevention using enum parameter binding
  • Guest list validation

145-163: LGTM - Status update now handles all BookingStatus values.

The switch statement now correctly handles PENDING along with all other status values, addressing the previous review concern.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +21 to +34
final PersistenceConfiguration cfg = new HibernatePersistenceConfiguration("emf")
.jdbcUrl("jdbc:mysql://localhost:3306/restaurant_booking")
.jdbcUsername(System.getenv("MYSQL_USER"))
.jdbcPassword(System.getenv("MYSQL_ROOT_PASSWORD"))
.property("hibernate.connection.provider_class", "org.hibernate.hikaricp.internal.HikariCPConnectionProvider")
.property("hibernate.hikari.maximumPoolSize", "10")
.property("hibernate.hikari.minimumIdle", "5")
.property("hibernate.hikari.idleTimeout", "300000")
.property("hibernate.hikari.connectionTimeout", "20000")
.property("hibernate.hbm2ddl.auto", "update")
.property("hibernate.show_sql", "true")
.property("hibernate.format_sql", "true")
.property("hibernate.highlight_sql", "true")
.managedClasses(entities);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Environment variable naming mismatch with docker-compose.yml.

The code uses MYSQL_USER for the username, but docker-compose.yml only defines MYSQL_ROOT_PASSWORD and MYSQL_DATABASE. If connecting as root, the username should be hardcoded or use a matching env var. Additionally, consider providing fallback defaults to prevent null connection strings.

🐛 Proposed fix
         final PersistenceConfiguration cfg = new HibernatePersistenceConfiguration("emf")
             .jdbcUrl("jdbc:mysql://localhost:3306/restaurant_booking")
-            .jdbcUsername(System.getenv("MYSQL_USER"))
-            .jdbcPassword(System.getenv("MYSQL_ROOT_PASSWORD"))
+            .jdbcUsername(System.getenv().getOrDefault("MYSQL_USER", "root"))
+            .jdbcPassword(System.getenv().getOrDefault("MYSQL_ROOT_PASSWORD", ""))

If using a non-root user, add MYSQL_USER and MYSQL_PASSWORD to docker-compose.yml environment section.

🤖 Prompt for AI Agents
In `@src/main/java/org/example/App.java` around lines 21 - 34, The JDBC credential
env vars are mismatched with docker-compose: the code calls
PersistenceConfiguration.jdbcUsername(System.getenv("MYSQL_USER")) and
jdbcPassword(System.getenv("MYSQL_ROOT_PASSWORD")) which can produce nulls;
update App.java to use consistent env vars and safe fallbacks: set jdbcUsername
to System.getenv("MYSQL_USER") != null ? System.getenv("MYSQL_USER") : "root"
(or another explicit default) and jdbcPassword to
System.getenv("MYSQL_PASSWORD") != null ? System.getenv("MYSQL_PASSWORD") :
System.getenv("MYSQL_ROOT_PASSWORD") (or a default), or alternatively change to
use a single matching pair (e.g., MYSQL_ROOT_PASSWORD and hardcoded "root"
username) and document adding MYSQL_USER/MYSQL_PASSWORD to docker-compose;
ensure the PersistenceConfiguration chain (jdbcUsername, jdbcPassword) never
receives null values.

Comment on lines +322 to +332
private static void showBookings(List<Booking> bookings) {
bookings.forEach(b -> {
System.out.println("\n📅 Booking ID: " + b.getId());
System.out.println(" Date: " + b.getDate());
System.out.println(" Time: " + b.getTimeSlot().getStartTime() + " - " + b.getTimeSlot().getFinishTime());
System.out.println(" Table: " + b.getTable().getTableNumber());
System.out.println(" Party Size: " + b.getParty());
System.out.println(" Status: " + b.getStatus());
System.out.println(" Guests: " + b.getGuests().stream().map(Guest::getName).toList());
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential NullPointerException if booking associations are null.

The display logic assumes b.getTimeSlot() and b.getTable() are never null. If a booking somehow has null associations (e.g., from database corruption or schema changes), this will throw NPE.

🛡️ Defensive null checks
     bookings.forEach(b -> {
         System.out.println("\n📅 Booking ID: " + b.getId());
         System.out.println("   Date: " + b.getDate());
-        System.out.println("   Time: " + b.getTimeSlot().getStartTime() + " - " + b.getTimeSlot().getFinishTime());
-        System.out.println("   Table: " + b.getTable().getTableNumber());
+        System.out.println("   Time: " + (b.getTimeSlot() != null ? b.getTimeSlot().getStartTime() + " - " + b.getTimeSlot().getFinishTime() : "N/A"));
+        System.out.println("   Table: " + (b.getTable() != null ? b.getTable().getTableNumber() : "N/A"));
         System.out.println("   Party Size: " + b.getParty());
         System.out.println("   Status: " + b.getStatus());
         System.out.println("   Guests: " + b.getGuests().stream().map(Guest::getName).toList());
     });
🤖 Prompt for AI Agents
In `@src/main/java/org/example/App.java` around lines 322 - 332, In showBookings,
guard against potential NPEs by null-checking booking associations before
dereferencing: for each Booking b (in showBookings), check b.getTimeSlot() and
b.getTable() (and b.getGuests()) and print safe defaults like "N/A" or an empty
list when null; use conditional expressions to obtain start/finish times only if
getTimeSlot() != null and table number only if getTable() != null, and handle
b.getGuests() == null by printing an empty list or "No guests" to avoid calling
stream() on null.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants