Changes between Version 3 and Version 4 of AdvancedApplicationDevelopment


Ignore:
Timestamp:
08/31/26 17:35:20 (7 days ago)
Author:
223091
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AdvancedApplicationDevelopment

    v3 v4  
    11= Advanced Application Development =
    22
    3 This page documents the advanced application development implemented for Phase P8 of the Room Reservation System project. The goal of this phase is to extend the Java prototype application with transaction handling and database connection pooling.
    4 
    5 The prototype was extended in two main directions:
    6 
    7 * database connection pooling was implemented using HikariCP;
    8 * explicit transaction handling was added for use-cases that execute multiple related database operations.
    9 
    10 The implementation affects the following source files:
    11 
    12 * ''src/main/java/mk/finki/roomreservation/App.java''
    13 * ''src/main/java/mk/finki/roomreservation/DatabasePool.java''
    14 * ''pom.xml''
    15 
    16 The application still connects to the same PostgreSQL database schema named ''project'' through the SSH tunnel on ''localhost:9999''.
    17 
    18 == Database Connection Pooling ==
    19 
    20 === Description ===
    21 
    22 In the previous version of the prototype, the application used a direct JDBC connection. In Phase P8, the application was changed to use a database connection pool.
    23 
    24 Connection pooling is useful because database connections are expensive to create repeatedly. Instead of creating a new physical database connection for every operation, the application initializes a pool of reusable connections. When a use-case needs database access, the application requests a connection from the pool, uses it, and then returns it to the pool.
    25 
    26 The connection pool is implemented in the class:
    27 
    28 {{{
    29 src/main/java/mk/finki/roomreservation/DatabasePool.java
    30 }}}
    31 
    32 The Maven configuration file ''pom.xml'' was also updated with the required HikariCP dependency.
    33 
    34 === Implementation ===
    35 
    36 The application initializes the connection pool after the user enters the database URL, database username, and database password. If the pool is created successfully, the application displays a confirmation message and then shows the main menu.
    37 
    38 [[Image(p8_connection_pool_initialized.png, width=100%)]]
    39 
    40 The screenshot shows that the HikariCP connection pool starts successfully. The application then confirms that the database connection pool was initialized and that the database connection is successful.
    41 
    42 The important confirmation messages are:
    43 
    44 {{{
    45 Database connection pool initialized successfully.
    46 
    47 Connected to database successfully.
    48 }}}
    49 
    50 After the connection pool is initialized, the application displays the main menu:
    51 
    52 {{{
    53 Main menu
    54 
    55 1. Search available rooms
    56 2. Create reservation request
    57 3. Approve or reject reservation
    58 4. Exit
    59    Choose option:
    60    }}}
    61 
    62 === Usage in the application ===
    63 
    64 The application uses connections from the pool in the implemented use-cases. A connection is requested from the pool before executing SQL statements. When the operation finishes, the connection is closed in the Java code, but this does not destroy the physical database connection. Instead, the connection is returned to the pool and can be reused.
    65 
    66 This improves the structure of the application because database access is managed through a separate pooling class instead of being handled manually in every part of the application.
    67 
    68 == Transactions ==
    69 
    70 Transactions are required when one logical operation contains multiple database operations that must succeed or fail together.
    71 
    72 In this prototype, transactions are used in the following implemented use-cases:
    73 
    74 * ''Create reservation request''
    75 * ''Approve or reject reservation''
    76 
    77 The application uses explicit transaction control in Java. The general transaction structure is:
    78 
    79 {{{
    80 connection.setAutoCommit(false);
    81 
    82 try {
    83 // execute several related SQL statements
    84 connection.commit();
    85 } catch (SQLException e) {
    86 connection.rollback();
    87 throw e;
    88 } finally {
    89 connection.setAutoCommit(true);
    90 }
    91 }}}
    92 
    93 This means that all database changes inside the transaction are saved only if the complete operation is successful. If an error occurs, the transaction is rolled back and the database is not left in a partially modified state.
    94 
    95 == Transaction 1: Create reservation request ==
    96 
    97 === Data requirements description ===
    98 
    99 Creating a reservation request may require changes in more than one table.
    100 
    101 A reservation request can include:
    102 
    103 * only a room;
    104 * only requested equipment;
    105 * both a room and requested equipment.
    106 
    107 When the requester creates a reservation with additional equipment, the application must insert data into:
    108 
    109 * ''project.reservations''
    110 * ''project.reservation_equipment''
    111 
    112 These operations must be executed as one transaction. If the reservation is inserted but inserting the requested equipment fails, the whole operation should be rolled back. This prevents incomplete reservation data from being saved.
    113 
    114 === Implementation ===
    115 
    116 The transaction is implemented in the ''Create reservation request'' use-case in ''App.java''.
    117 
    118 The application performs the following steps:
    119 
    120 1. The requester chooses the option ''Create reservation request'' from the main menu.
    121 2. The system displays a list of users.
    122 3. The requester selects the requester from the list.
    123 4. The system asks for the reservation date.
    124 5. The requester enters the reservation date.
    125 6. The system asks for the start time and end time.
    126 7. The requester enters the requested time interval.
    127 8. The system asks whether the reservation should include a room.
    128 9. If the reservation includes a room, the system displays available rooms.
    129 10. The requester selects one of the available rooms from the list.
    130 11. The system asks whether additional/general equipment should be requested.
    131 12. If equipment is requested, the requester selects equipment and requested quantity.
    132 13. The application starts a transaction.
    133 14. The application inserts a new record into ''project.reservations''.
    134 15. If requested equipment exists, the application inserts corresponding records into ''project.reservation_equipment''.
    135 16. If all operations are successful, the transaction is committed.
    136 17. If an error occurs, the transaction is rolled back.
    137 
    138 === Demo execution ===
    139 
    140 The following screenshot shows the successful execution of the create reservation transaction.
    141 
    142 [[Image(p8_create_reservation_transaction_success.png, width=100%)]]
    143 
    144 In this test, the requester creates a reservation request through the Java prototype. The application displays available users and rooms, the requester selects values from the displayed lists, and the reservation request is created successfully.
    145 
    146 The use-case demonstrates that the user does not need to remember internal database identifiers such as ''user_id'' or ''room_id''. Instead, the application lists the possible values and internally uses the selected database identifiers.
    147 
    148 === Database verification ===
    149 
    150 The result can be verified in DBeaver by checking the created reservation and the requested equipment records.
    151 
    152 {{{
    153 SELECT *
    154 FROM project.reservations
    155 ORDER BY reservation_id DESC;
    156 
    157 SELECT *
    158 FROM project.reservation_equipment
    159 ORDER BY reservation_id DESC;
    160 }}}
    161 
    162 The following screenshot shows the database verification after the transaction was executed.
    163 
    164 [[Image(p8_create_reservation_database_verification.png, width=100%)]]
    165 
    166 The screenshot confirms that the reservation was inserted into ''project.reservations''. If the reservation included additional requested equipment, the related records are also inserted into ''project.reservation_equipment''.
    167 
    168 === Transaction discussion ===
    169 
    170 This use-case requires a transaction because creating a reservation request is one logical operation, even when it modifies multiple tables. The reservation and its requested equipment must be saved together. If one part fails, the whole operation should be cancelled.
    171 
    172 Therefore, the application commits the transaction only after all related inserts are successful.
    173 
    174 == Transaction 2: Approve or reject reservation ==
    175 
    176 === Data requirements description ===
    177 
    178 Approving or rejecting a reservation also requires multiple related database operations.
    179 
    180 When an approver makes a decision, the application must:
    181 
    182 * update the reservation status in ''project.reservations'';
    183 * insert a new approval decision into ''project.approvals''.
    184 
    185 These two operations must be consistent. The reservation status and the approval record must match. If the approval record cannot be inserted, the reservation status should not remain changed.
    186 
    187 === Implementation ===
    188 
    189 The transaction is implemented in the ''Approve or reject reservation'' use-case in ''App.java''.
    190 
    191 The application performs the following steps:
    192 
    193 1. The approver chooses the option ''Approve or reject reservation'' from the main menu.
    194 2. The system displays a list of pending reservations.
    195 3. The approver selects one reservation from the list.
    196 4. The system displays a list of users who can approve reservations.
    197 5. The approver selects the approving user.
    198 6. The approver chooses whether the reservation should be approved or rejected.
    199 7. The approver enters an optional decision note.
    200 8. The application starts a transaction.
    201 9. The application updates the selected reservation status in ''project.reservations''.
    202 10. The application inserts a new approval record into ''project.approvals''.
    203 11. If both operations are successful, the transaction is committed.
    204 12. If an error occurs, the transaction is rolled back.
    205 
    206 === Demo execution ===
    207 
    208 The following screenshot shows the successful execution of the approval transaction.
    209 
    210 [[Image(p8_approval_transaction_success.png, width=100%)]]
    211 
    212 The screenshot shows that the approver selects a pending reservation, chooses the approver user, enters the decision, and saves the approval decision. The application then displays the final reservation details.
    213 
    214 === Database verification ===
    215 
    216 The result can be verified in DBeaver by checking the reservations and approvals tables.
    217 
    218 {{{
    219 SELECT *
    220 FROM project.reservations
    221 ORDER BY reservation_id DESC;
    222 
    223 SELECT *
    224 FROM project.approvals
    225 ORDER BY approval_id DESC;
    226 }}}
    227 
    228 The following screenshot shows the database verification after the approval transaction was executed.
    229 
    230 [[Image(p8_approval_database_verification.png, width=100%)]]
    231 
    232 The screenshot confirms that the reservation status was updated and that a corresponding approval record was inserted into ''project.approvals''.
    233 
    234 === Transaction discussion ===
    235 
    236 This use-case requires a transaction because approval is not only a single table change. The status update and the approval insert represent one logical decision.
    237 
    238 If the reservation status were updated without inserting an approval record, the database would lose information about who made the decision, when the decision was made, and what note was entered. Therefore, both operations must be committed together or rolled back together.
    239 
    240 == Rollback behavior ==
    241 
    242 The transaction implementation protects the database from partial changes.
    243 
    244 If an error occurs during the create reservation transaction, the inserted reservation and related requested equipment records are rolled back. If an error occurs during the approval transaction, the reservation status update and the approval insert are rolled back.
    245 
    246 This behavior preserves database consistency because incomplete multi-step operations are not saved.
    247 
    248 == Maven configuration ==
    249 
    250 The Maven configuration file ''pom.xml'' was updated to include the required dependencies.
    251 
    252 The application uses:
    253 
    254 * PostgreSQL JDBC driver for connecting to the PostgreSQL database;
    255 * HikariCP for database connection pooling;
    256 * SLF4J implementation for HikariCP logging.
    257 
    258 The relevant configuration is stored in:
    259 
    260 {{{
    261 pom.xml
    262 }}}
    263 
    264 == Final discussion ==
    265 
    266 Phase P8 improves the Java prototype by adding transaction handling and database connection pooling.
    267 
    268 The most important improvements are:
    269 
    270 * the application uses HikariCP connection pooling;
    271 * database access is centralized through ''DatabasePool.java'';
    272 * the connection pool is initialized once and reused by the application;
    273 * creating a reservation request is executed as a transaction;
    274 * inserting requested equipment is included in the same transaction as the reservation insert;
    275 * approving or rejecting a reservation is executed as a transaction;
    276 * reservation status updates and approval records are saved consistently;
    277 * rollback is used when a multi-step database operation fails;
    278 * the prototype is more reliable and closer to a real database application.
    279 
    280 These additions satisfy the Phase P8 requirements because the application now demonstrates both database connection pooling and transactions for use-cases where multiple related database operations must be executed as one unit of work.
     3== Overview ==
     4
     5This phase documents the advanced application-level implementation of the Room Reservation System. The final application is implemented as a full-stack system with a Spring Boot backend and a React frontend.
     6
     7The focus of this phase is on:
     8
     9 * database transactions
     10 * connection pooling
     11 * database access through the backend application
     12 * consistency between the documented implementation and the source code uploaded in the project Git repository
     13
     14The relevant source files are available in the project repository:
     15
     16 * [source:backend/src/main/resources/application.properties backend/src/main/resources/application.properties]
     17 * [source:backend/src/main/java/mk/finki/roomreservation/service/ReservationService.java backend/src/main/java/mk/finki/roomreservation/service/ReservationService.java]
     18 * [source:backend/src/main/java/mk/finki/roomreservation/service/AuthService.java backend/src/main/java/mk/finki/roomreservation/service/AuthService.java]
     19 * [source:backend/src/main/java/mk/finki/roomreservation/service/ApprovalService.java backend/src/main/java/mk/finki/roomreservation/service/ApprovalService.java]
     20 * [source:backend/src/main/java/mk/finki/roomreservation/controller/ReservationController.java backend/src/main/java/mk/finki/roomreservation/controller/ReservationController.java]
     21 * [source:backend/src/main/java/mk/finki/roomreservation/controller/AuthController.java backend/src/main/java/mk/finki/roomreservation/controller/AuthController.java]
     22 * [source:backend/src/main/java/mk/finki/roomreservation/controller/ApprovalController.java backend/src/main/java/mk/finki/roomreservation/controller/ApprovalController.java]
     23
     24== Connection Pooling ==
     25
     26Connection pooling is implemented through Spring Boot's default HikariCP integration. The backend application uses `JdbcTemplate` for database access, while Spring Boot manages database connections internally through a HikariCP connection pool.
     27
     28The pool configuration is placed in:
     29
     30[source:backend/src/main/resources/application.properties backend/src/main/resources/application.properties]
     31
     32The relevant configuration is:
     33
     34{{{
     35spring.datasource.hikari.pool-name=RoomReservationPool
     36spring.datasource.hikari.maximum-pool-size=5
     37spring.datasource.hikari.minimum-idle=1
     38spring.datasource.hikari.connection-timeout=10000
     39}}}
     40
     41The database connection parameters are also configured in the same file:
     42
     43{{{
     44spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:9999/db_202526z_va_prj_room_reservation}
     45spring.datasource.username=${SPRING_DATASOURCE_USERNAME:db_202526z_va_prj_room_reservation_owner}
     46spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:}
     47}}}
     48
     49The actual database password is not hard-coded in the source code. It is provided through an environment variable. This makes the application safer and allows the same codebase to run locally and through Docker.
     50
     51The backend services do not manually open and close raw JDBC connections. Instead, they use Spring's `JdbcTemplate`. When a query is executed, Spring obtains a connection from the HikariCP pool, executes the SQL operation, and returns the connection back to the pool.
     52
     53== Transaction 1: Creating a Reservation Request ==
     54
     55The main application-level transaction is implemented in:
     56
     57[source:backend/src/main/java/mk/finki/roomreservation/service/ReservationService.java backend/src/main/java/mk/finki/roomreservation/service/ReservationService.java]
     58
     59The transaction is used in the `createReservation` method. This method inserts a new reservation into `project.reservations` and then optionally inserts one or more related equipment records into `project.reservation_equipment`.
     60
     61The method is marked with `@Transactional`:
     62
     63{{{
     64@Transactional
     65public ReservationDetails createReservation(CreateReservationRequest request) {
     66    validateCreateRequest(request);
     67
     68    LocalDate date = LocalDate.parse(request.reservationDate());
     69    LocalTime start = LocalTime.parse(request.startTime());
     70    LocalTime end = LocalTime.parse(request.endTime());
     71
     72    List<EquipmentRequest> equipment =
     73            request.equipment() == null ? List.of() : request.equipment();
     74
     75    String insertReservationSql = """
     76            INSERT INTO project.reservations (
     77                room_id,
     78                user_id,
     79                reservation_date,
     80                start_time,
     81                end_time,
     82                status
     83            )
     84            VALUES (?, ?, ?, ?, ?, 'pending')
     85            RETURNING reservation_id
     86            """;
     87
     88    Integer reservationId = jdbcTemplate.queryForObject(
     89            insertReservationSql,
     90            Integer.class,
     91            request.roomId(),
     92            request.userId(),
     93            Date.valueOf(date),
     94            Time.valueOf(start),
     95            Time.valueOf(end)
     96    );
     97
     98    if (reservationId == null) {
     99        throw new RuntimeException("Reservation was not created.");
     100    }
     101
     102    for (EquipmentRequest item : equipment) {
     103        jdbcTemplate.update(
     104                """
     105                INSERT INTO project.reservation_equipment (
     106                    reservation_id,
     107                    equipment_id,
     108                    requested_quantity
     109                )
     110                VALUES (?, ?, ?)
     111                """,
     112                reservationId,
     113                item.equipmentId(),
     114                item.requestedQuantity()
     115        );
     116    }
     117
     118    return findReservationDetails(reservationId);
     119}
     120}}}
     121
     122This transaction protects the database from partial writes.
     123
     124If the reservation insert succeeds, but one of the equipment inserts fails, the entire transaction is rolled back. This means that the system will not store a reservation without its required equipment records.
     125
     126The method also validates the request before writing to the database:
     127
     128{{{
     129private void validateCreateRequest(CreateReservationRequest request) {
     130    if (request.userId() == null) {
     131        throw new IllegalArgumentException("Requester user is required.");
     132    }
     133
     134    if (request.reservationDate() == null || request.reservationDate().isBlank()) {
     135        throw new IllegalArgumentException("Reservation date is required.");
     136    }
     137
     138    if (request.startTime() == null || request.startTime().isBlank()) {
     139        throw new IllegalArgumentException("Start time is required.");
     140    }
     141
     142    if (request.endTime() == null || request.endTime().isBlank()) {
     143        throw new IllegalArgumentException("End time is required.");
     144    }
     145
     146    LocalTime start = LocalTime.parse(request.startTime());
     147    LocalTime end = LocalTime.parse(request.endTime());
     148
     149    if (!end.isAfter(start)) {
     150        throw new IllegalArgumentException("End time must be after start time.");
     151    }
     152
     153    boolean hasRoom = request.roomId() != null;
     154    boolean hasEquipment = request.equipment() != null && !request.equipment().isEmpty();
     155
     156    if (!hasRoom && !hasEquipment) {
     157        throw new IllegalArgumentException("Reservation must include at least one resource: room or equipment.");
     158    }
     159}
     160}}}
     161
     162This validates that the reservation has a requester, a valid date and time interval, and at least one reserved resource.
     163
     164== Transaction 2: User Registration ==
     165
     166User registration is implemented in:
     167
     168[source:backend/src/main/java/mk/finki/roomreservation/service/AuthService.java backend/src/main/java/mk/finki/roomreservation/service/AuthService.java]
     169
     170The `register` method is also transactional because it writes to two related tables:
     171
     172 * `project.users`
     173 * `project.user_credentials`
     174
     175The method first inserts the user profile and then inserts the password hash.
     176
     177{{{
     178@Transactional
     179public UserOption register(RegisterRequest request) {
     180    validateRegisterRequest(request);
     181
     182    String fullName = request.fullName().trim();
     183    String username = request.username().trim();
     184    String email = request.email().trim();
     185
     186    if (usernameExists(username)) {
     187        throw new IllegalArgumentException("Username is already taken.");
     188    }
     189
     190    if (emailExists(email)) {
     191        throw new IllegalArgumentException("Email is already registered.");
     192    }
     193
     194    Integer userId = jdbcTemplate.queryForObject(
     195            """
     196            INSERT INTO project.users (
     197                username,
     198                email,
     199                full_name,
     200                role
     201            )
     202            VALUES (?, ?, ?, 'regular')
     203            RETURNING user_id
     204            """,
     205            Integer.class,
     206            username,
     207            email,
     208            fullName
     209    );
     210
     211    if (userId == null) {
     212        throw new RuntimeException("User registration failed.");
     213    }
     214
     215    String passwordHash = passwordEncoder.encode(request.password());
     216
     217    jdbcTemplate.update(
     218            """
     219            INSERT INTO project.user_credentials (
     220                user_id,
     221                password_hash
     222            )
     223            VALUES (?, ?)
     224            """,
     225            userId,
     226            passwordHash
     227    );
     228
     229    return findUserById(userId);
     230}
     231}}}
     232
     233This transaction ensures that the application does not create an incomplete account. If the password hash insert fails, the inserted user is rolled back as well.
     234
     235Passwords are not stored as plain text. The application stores only the BCrypt password hash.
     236
     237== Transaction 3: Activating an Existing Account ==
     238
     239The application also supports assigning a password to an already existing database user. This is implemented in the same service:
     240
     241[source:backend/src/main/java/mk/finki/roomreservation/service/AuthService.java backend/src/main/java/mk/finki/roomreservation/service/AuthService.java]
     242
     243{{{
     244@Transactional
     245public UserOption activateExistingAccount(ActivateAccountRequest request) {
     246    validateActivationRequest(request);
     247
     248    UserOption user = findUserByIdentifier(request.identifier());
     249
     250    if (credentialsExist(user.userId())) {
     251        throw new IllegalArgumentException("This account already has a password.");
     252    }
     253
     254    String passwordHash = passwordEncoder.encode(request.password());
     255
     256    jdbcTemplate.update(
     257            """
     258            INSERT INTO project.user_credentials (
     259                user_id,
     260                password_hash
     261            )
     262            VALUES (?, ?)
     263            """,
     264            user.userId(),
     265            passwordHash
     266    );
     267
     268    return user;
     269}
     270}}}
     271
     272This keeps the user account and credential creation consistent.
     273
     274== Stored Function Call for Approval Workflow ==
     275
     276Approving or rejecting a reservation is implemented in:
     277
     278[source:backend/src/main/java/mk/finki/roomreservation/service/ApprovalService.java backend/src/main/java/mk/finki/roomreservation/service/ApprovalService.java]
     279
     280The Java backend calls a PostgreSQL stored function:
     281
     282{{{
     283public ApprovalResult approveOrReject(ApprovalRequest request) {
     284    validateApprovalRequest(request);
     285
     286    String sql = """
     287            SELECT *
     288            FROM project.fn_approve_or_reject_reservation(
     289                ?,
     290                ?,
     291                CAST(? AS project.approval_decision_domain),
     292                ?
     293            )
     294            """;
     295
     296    ApprovalResult result = jdbcTemplate.queryForObject(
     297            sql,
     298            (rs, rowNum) -> new ApprovalResult(
     299                    rs.getInt("out_approval_id"),
     300                    rs.getInt("out_reservation_id"),
     301                    rs.getString("out_status"),
     302                    rs.getString("out_decision"),
     303                    rs.getString("out_decision_time")
     304            ),
     305            request.reservationId(),
     306            request.approverId(),
     307            request.decision(),
     308            request.note()
     309    );
     310
     311    if (result == null) {
     312        throw new RuntimeException("Approval operation failed.");
     313    }
     314
     315    return result;
     316}
     317}}}
     318
     319The approval logic is intentionally placed inside the database stored function. This demonstrates cooperation between the application layer and the database layer.
     320
     321The backend validates the request before calling the function:
     322
     323{{{
     324private void validateApprovalRequest(ApprovalRequest request) {
     325    if (request.reservationId() <= 0) {
     326        throw new IllegalArgumentException("Reservation ID is required.");
     327    }
     328
     329    if (request.approverId() <= 0) {
     330        throw new IllegalArgumentException("Approver ID is required.");
     331    }
     332
     333    if (request.decision() == null || request.decision().isBlank()) {
     334        throw new IllegalArgumentException("Decision is required.");
     335    }
     336
     337    if (!request.decision().equals("approved") && !request.decision().equals("rejected")) {
     338        throw new IllegalArgumentException("Decision must be approved or rejected.");
     339    }
     340}
     341}}}
     342
     343== Controllers Using the Transactional Services ==
     344
     345The transactional service methods are exposed through REST controllers.
     346
     347Reservation creation is exposed through:
     348
     349[source:backend/src/main/java/mk/finki/roomreservation/controller/ReservationController.java backend/src/main/java/mk/finki/roomreservation/controller/ReservationController.java]
     350
     351{{{
     352@PostMapping("/api/reservations")
     353public ReservationDetails createReservation(@RequestBody CreateReservationRequest request) {
     354    return reservationService.createReservation(request);
     355}
     356}}}
     357
     358User registration and login are exposed through:
     359
     360[source:backend/src/main/java/mk/finki/roomreservation/controller/AuthController.java backend/src/main/java/mk/finki/roomreservation/controller/AuthController.java]
     361
     362{{{
     363@PostMapping("/register")
     364public UserOption register(@RequestBody RegisterRequest request) {
     365    return authService.register(request);
     366}
     367}}}
     368
     369Approval is exposed through:
     370
     371[source:backend/src/main/java/mk/finki/roomreservation/controller/ApprovalController.java backend/src/main/java/mk/finki/roomreservation/controller/ApprovalController.java]
     372
     373{{{
     374@PostMapping("/api/approvals")
     375public ApprovalResult approveOrReject(@RequestBody ApprovalRequest request) {
     376    return approvalService.approveOrReject(request);
     377}
     378}}}
     379
     380== Error Handling and Rollback Behavior ==
     381
     382The application uses a global exception handler:
     383
     384[source:backend/src/main/java/mk/finki/roomreservation/exception/ApiExceptionHandler.java backend/src/main/java/mk/finki/roomreservation/exception/ApiExceptionHandler.java]
     385
     386If a validation error or database error occurs, the backend returns a structured error response to the frontend.
     387
     388Because the important write operations are marked with `@Transactional`, Spring rolls back the transaction when a runtime exception occurs.
     389
     390This behavior is important for:
     391
     392 * reservation creation
     393 * user registration
     394 * account activation
     395
     396== Summary ==
     397
     398The final application implements advanced application development concepts through:
     399
     400 * Spring Boot REST controllers
     401 * service-layer database access
     402 * Spring `JdbcTemplate`
     403 * HikariCP connection pooling
     404 * transaction management with `@Transactional`
     405 * database stored function integration
     406 * environment-based database credentials
     407 * BCrypt password hashing
     408
     409The documentation is aligned with the final Git source code. The previously used prototype class names are no longer referenced. The current implementation documents the actual Spring Boot files that are present in the repository.