1515import com .itasocialacademy .oitassist .competition .exceptions .StaleEntityVersionException ;
1616import com .itasocialacademy .oitassist .competition .spi .ParticipationInquiryPort ;
1717import com .itasocialacademy .oitassist .security .api .interfaces .SecurityFacade ;
18+ import jakarta .annotation .PostConstruct ;
1819import jakarta .persistence .EntityManager ;
20+ import jakarta .persistence .PersistenceContext ;
1921import java .time .ZonedDateTime ;
2022import java .util .List ;
2123import java .util .Objects ;
2224import java .util .Optional ;
2325import lombok .RequiredArgsConstructor ;
26+ import org .springframework .beans .factory .annotation .Value ;
2427import org .springframework .security .access .AccessDeniedException ;
2528import org .springframework .stereotype .Component ;
2629import org .springframework .transaction .annotation .Transactional ;
@@ -33,8 +36,23 @@ public class HierarchyValidator {
3336 private final TourRepository tourRepository ;
3437 private final SecurityFacade securityFacade ;
3538 private final ParticipationInquiryPort participationInquiryPort ;
39+
40+ @ PersistenceContext
3641 private final EntityManager entityManager ;
3742
43+ @ Value ("${competition.hierarchy-lock-timeout-ms:3000}" )
44+ private int hierarchyLockTimeoutMs ;
45+
46+ @ PostConstruct
47+ void validateLockTimeoutConfig () {
48+ if (hierarchyLockTimeoutMs <= 0 ) {
49+ throw new IllegalStateException (
50+ "competition.hierarchy-lock-timeout-ms must be positive "
51+ + "(0 disables the Postgres lock timeout entirely, causing indefinite waits); got "
52+ + hierarchyLockTimeoutMs );
53+ }
54+ }
55+
3856 @ Transactional (readOnly = true )
3957 public void checkVisibilityAccess (Long competitionId ) {
4058 Competition competition = competitionRepository .findById (competitionId )
@@ -373,8 +391,11 @@ public void validateEntityVersion(Long expectedVersion, Long actualVersion, Clas
373391 */
374392 @ Transactional
375393 public Competition lockCompetitionForUpdate (Long competitionId ) {
394+ // Postgres does not support bind parameters for SET commands, so the
395+ // value must be inlined. Safe — hierarchyLockTimeoutMs is a validated positive
396+ // int from server configuration (@Value), never derived from request input.
376397 entityManager .createNativeQuery (
377- "SET LOCAL lock_timeout = '3000ms'" ).executeUpdate ();
398+ "SET LOCAL lock_timeout = '%dms'" . formatted ( hierarchyLockTimeoutMs ) ).executeUpdate ();
378399 return competitionRepository .findByIdForUpdate (competitionId )
379400 .orElseThrow (() -> new CompetitionNotFoundException (competitionId ));
380401 }
0 commit comments