2023-05-24-TIL
2023-05-24-TIL
Today I Learned
Spring Boot의 요청 값 검증
Bean Validation에서 가장 헷갈리는 세 가지 제약의 차이는 다음과 같다.
@NotNull:null만 막는다. 빈 문자열""은 통과한다.@NotEmpty:null과 길이 0을 막는다. 문자열, 컬렉션, 배열, 맵에 쓸 수 있고 공백 문자열" "은 통과한다.@NotBlank: 문자열 전용이며null, 빈 문자열, 공백만 있는 문자열을 모두 막는다.
전화번호처럼 형식이 있는 값은 @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$")처럼 정규식으로 검사한다. 이동욱 님의 글은 검증 실패 응답을 공통 모듈로 묶어 모든 API가 같은 형식의 오류를 돌려주게 하는 구성을 다룬다.
- https://medium.com/@techisbeautiful/mastering-data-validation-in-spring-boot-best-practices-and-expert-tips-for-robust-and-secure-ba24dd79bc0a
- https://jojoldu.tistory.com/129
- https://www.baeldung.com/java-bean-validation-not-null-empty-blank
- https://stackoverflow.com/questions/31143315/regex-phone-number-with-dashes
Spring Validator
애너테이션으로 표현하기 어려운 검증(두 필드의 관계, DB 조회가 필요한 규칙)은 Spring의 org.springframework.validation.Validator 인터페이스로 구현할 수 있다. supports(Class<?>)로 검증 대상 타입을 밝히고, validate(Object target, Errors errors)에서 규칙을 어기면 errors.rejectValue(...)로 오류를 등록한다. ValidationUtils는 rejectIfEmptyOrWhitespace 같은 자주 쓰는 검사를 정적 메서드로 제공한다. 컨트롤러에서는 @InitBinder로 WebDataBinder에 이 검증기를 추가하면 @Valid와 함께 실행된다. Bean Validation 애너테이션으로 해결되는 것은 애너테이션으로 두고, 그렇지 않은 규칙만 Validator로 빼는 것이 관리하기 쉽다.
- https://www.baeldung.com/spring-boot-bean-validation
- https://donghyeon.dev/spring/2019/04/15/Spring%EC%97%90%EC%84%9C-validation-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0/
- https://reflectoring.io/bean-validation-with-spring-boot/
- https://docs.spring.io/spring-framework/docs/3.0.x/reference/index.html
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation/ValidationUtils.html
Spring Data JPA의 exists 쿼리
존재 여부만 알고 싶을 때는 Spring Data JPA의 파생 쿼리 existsByEmail(String email)을 쓸 수 있다. 문제는 조건이 복잡해 @Query나 Querydsl로 직접 작성할 때다. 이동욱 님의 글에 따르면 JPQL은 SELECT EXISTS 구문을 지원하지 않아 COUNT(...) > 0으로 우회하는 경우가 많고, Querydsl의 exists()도 내부적으로 count를 쓴다. count는 조건에 맞는 행을 모두 세야 하지만 존재 확인은 첫 행을 찾는 순간 끝낼 수 있으므로, 큰 테이블에서는 차이가 커진다. 글이 권하는 방법은 selectOne().from(...).where(...).fetchFirst()처럼 limit 1로 한 건만 조회하고 결과가 null인지로 판단하는 것이다.
- https://www.baeldung.com/spring-data-exists-query
- https://jojoldu.tistory.com/516
SELECT 분산과 다중 서버 동시성
이 묶음에는 두 가지 주제가 섞여 있다.
첫째는 읽기 부하 분산이다. 복제(replication)로 같은 데이터를 가진 DB를 여러 대 두면 쓰기는 마스터로, SELECT는 복제본으로 나눠 보내 읽기 부하를 분산할 수 있다. CUBRID 튜토리얼은 JDBC URL의 althosts와 loadBalance=true 옵션으로 연결 대상 호스트 순서를 무작위로 섞어 SELECT를 여러 서버에 퍼뜨리는 구성을 설명한다. Spring에서는 AbstractRoutingDataSource로 트랜잭션이 읽기 전용인지에 따라 데이터소스를 고르는 방식을 많이 쓴다. 복제 지연이 있으므로 방금 쓴 데이터를 바로 읽어야 하는 요청은 마스터로 보내야 한다. 분산 쿼리와 서브 쿼리 팩토링 글은 Oracle에서 DB 링크로 원격 테이블을 참조하는 WITH 절이 어디서 실행되는지를 실행 계획으로 비교한다.
둘째는 우아한형제들의 MySQL 분산 락 글이다. 서버가 여러 대이면 애플리케이션의 synchronized로는 “사용자당 카드 2장” 같은 규칙을 지킬 수 없다. 글은 MySQL의 사용자 수준 락 함수 GET_LOCK(name, timeout)과 RELEASE_LOCK(name)으로 여러 서버에 걸친 임계 구역을 만든다. 핵심 함정은 커넥션이다. 이 락은 세션(커넥션)에 묶이므로, 커넥션 풀에서 매번 다른 커넥션을 받으면 락을 얻은 커넥션과 해제하는 커넥션이 달라져 해제에 실패한다. 글은 최종적으로 DataSource에서 커넥션을 직접 꺼내 락 획득과 해제를 같은 커넥션으로 처리했다.
- https://techblog.woowahan.com/2631/
- https://www.cubrid.com/qna/3796337
- https://www.cubrid.com/tutorial/3794192
- https://hrjeong.tistory.com/365
- AbstractRoutingDataSource (Spring Framework Javadoc)
댓글
아직 댓글이 없습니다