Skip to content

Commit e8be2f8

Browse files
committed
feat: set lock_timeout in lockCompetitionForUpdate() using entityManager
1 parent 24e6939 commit e8be2f8

1 file changed

Lines changed: 22 additions & 1 deletion

File tree

src/main/java/com/itasocialacademy/oitassist/competition/validation/HierarchyValidator.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
import com.itasocialacademy.oitassist.competition.exceptions.StaleEntityVersionException;
1616
import com.itasocialacademy.oitassist.competition.spi.ParticipationInquiryPort;
1717
import com.itasocialacademy.oitassist.security.api.interfaces.SecurityFacade;
18+
import jakarta.annotation.PostConstruct;
1819
import jakarta.persistence.EntityManager;
20+
import jakarta.persistence.PersistenceContext;
1921
import java.time.ZonedDateTime;
2022
import java.util.List;
2123
import java.util.Objects;
2224
import java.util.Optional;
2325
import lombok.RequiredArgsConstructor;
26+
import org.springframework.beans.factory.annotation.Value;
2427
import org.springframework.security.access.AccessDeniedException;
2528
import org.springframework.stereotype.Component;
2629
import 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

Comments
 (0)