Let the front desk renew a membership for a month

Java · Spring Boot · beginner · modification

Members who pay cash at the front desk currently need a manager to edit their renewal date by hand. This adds `POST /api/front-desk/memberships/{id}/renewal`, which the till calls once a payment for one month has gone through. `MembershipRenewalService.renewForOneMonth` loads the membership with a row lock (a new `findLockedById` with `PESSIMISTIC_WRITE`), so two tills renewing the same member queue up instead of overwriting each other, extends `renewsOn` by a month and returns the updated view; the transaction commits when the method returns. An unknown id becomes a 404 through `ResponseStatusException`.

Gym management backend on Spring Boot 3.3 (Java 21), Spring Data JPA (Hibernate 6) on PostgreSQL, Spring Security. The existing, unchanged `SecurityConfig` lets only staff (`ROLE_STAFF`) call `/api/front-desk/**`. Existing and unchanged: the `Membership` entity (`id`, `memberId`, and `renewsOn`, a non-null `LocalDate` with a setter) and the `MembershipView` record with `MembershipView.from(membership)`. `renewsOn` is the first day the membership is no longer paid for: the door scanner admits a member while the club's local date is before `renewsOn`. Signup only accepts start dates on the 1st to the 28th of a month, so every `renewsOn` falls on day 1 to 28, and all dates lie between 2020 and 2100. The till sends exactly one renewal request per completed payment.

Requirements

Files touched

--- src/main/java/com/acme/gym/memberships/MembershipRepository.java
 package com.acme.gym.memberships;
 
+import jakarta.persistence.LockModeType;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Lock;
+import java.util.Optional;
 
 public interface MembershipRepository extends JpaRepository<Membership, Long> {
+
+    @Lock(LockModeType.PESSIMISTIC_WRITE)
+    Optional<Membership> findLockedById(Long id);
 }
 
--- src/main/java/com/acme/gym/memberships/MembershipRenewalController.java
+package com.acme.gym.memberships;
+
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/front-desk/memberships")
+public class MembershipRenewalController {
+
+    private final MembershipRenewalService renewalService;
+
+    MembershipRenewalController(MembershipRenewalService renewalService) {
+        this.renewalService = renewalService;

Review this PR

Java practice