포스트

2022-05-20-TIL

2022-05-20-TIL

Today I Learned

REST API Authorization Best Practices

Stack Overflow 블로그 글이 정리한 REST API 인증·인가 원칙은 다음과 같다. 모든 API는 TLS 위에서 제공한다. 사용자 인증은 비밀번호를 직접 관리하기보다 OAuth2와 OpenID Connect로 외부 IdP에 맡긴다. 프로그램 접근용으로는 사용자 계정에 묶인 API 키를 발급하고, 키마다 read/write 같은 권한 범위를 나눈다. 브라우저에 토큰을 둘 때는 localStorage보다 SameSite=Strict 같은 속성을 준 보안 쿠키를 권한다. 인증 여부 같은 공통 검사는 미들웨어에서, 리소스 소유자 확인 같은 복잡한 인가는 애플리케이션 코드에서 처리한다.

  • https://stackoverflow.blog/2021/10/06/best-practices-for-authentication-and-authorization-for-rest-apis/

역할에 따라 Jackson JSON 출력 필터링

같은 엔티티를 반환하더라도 관리자에게는 모든 필드를, 일반 사용자에게는 일부만 보여 주고 싶을 때 Jackson의 @JsonView를 쓸 수 있다. 역할별 뷰 클래스를 정의하고 필드에 @JsonView(View.Admin.class)처럼 표시한 뒤, 응답 직전에 현재 사용자의 권한을 보고 어떤 뷰로 직렬화할지 정한다. Spring MVC에서는 MappingJacksonValue에 뷰를 지정하거나 ResponseBodyAdvice에서 일괄로 적용하는 방식이 쓰인다.

  • https://www.baeldung.com/spring-security-role-filter-json

MyBatis 객체 안의 객체 매핑

MyBatis에서 조회 결과를 중첩 객체로 받으려면 resultMap의 association(1:1)과 collection(1:N)을 쓴다. 조인 쿼리 한 번의 결과를 컬럼 접두사로 나눠서 매핑하는 중첩 결과 방식과, 별도 select를 지정해서 연관 객체를 따로 조회하는 중첩 쿼리 방식이 있다. 중첩 쿼리 방식은 목록 조회에서 행마다 쿼리가 추가로 나가는 N+1 문제가 생기기 쉽다.

  • https://umbum.dev/1198

QueryDSL

QueryDSL은 엔티티로부터 생성한 Q 클래스를 이용해 JPQL을 자바 코드로 작성하게 해 주는 라이브러리다. 문자열 JPQL과 달리 필드명 오타나 타입 불일치가 컴파일 시점에 드러나고, BooleanBuilder나 where에 null을 넘기면 무시되는 성질을 이용해 동적 조건을 조립하기 쉽다. Spring Data JPA와 함께 쓸 때는 사용자 정의 리포지토리 인터페이스와 구현 클래스(...RepositoryImpl)를 두고 그 안에서 JPAQueryFactory를 사용하는 구성이 일반적이다.

  • https://devwithpug.github.io/java/querydsl-with-datajpa/

@Query in JPA

Spring Data JPA의 @Query는 메서드 이름으로 표현하기 어려운 쿼리를 리포지토리 메서드에 직접 적는 방법이다. 기본은 JPQL이고 nativeQuery = true로 SQL을 쓸 수 있다. 파라미터는 :name과 @Param으로 바인딩한다. UPDATE나 DELETE 쿼리에는 @Modifying이 필요하고, 벌크 연산은 영속성 컨텍스트를 거치지 않으므로 이후 같은 트랜잭션에서 엔티티를 다시 읽을 때 clearAutomatically = true 등으로 컨텍스트를 비워야 오래된 값을 보지 않는다.

  • https://jforj.tistory.com/90

JPQL

JPQL은 테이블이 아니라 엔티티 객체와 그 필드를 대상으로 쓰는 쿼리 언어다. SELECT m FROM Member m WHERE m.team.name = :name처럼 클래스명과 필드명을 쓰고, JPA 구현체가 이를 실제 DB 방언의 SQL로 번역한다. 그래서 특정 RDBMS에 의존하지 않는다는 장점이 있지만, DB 고유 함수나 복잡한 쿼리는 네이티브 쿼리가 필요할 때가 있다.

  • https://wikidocs.net/157864#:~:text=JPQL%EC%9D%80%20%ED%8A%B9%EC%A0%95%20RDBMS%EC%97%90,%22%ED%81%B4%EB%9E%98%EC%8A%A4%22%EA%B0%80%20%EA%B8%B0%EC%88%A0%EB%90%A9%EB%8B%88%EB%8B%A4.

JPAQuery::fetchResults is deprecated

Querydsl 5.0에서 fetchResults()와 fetchCount()가 deprecated 되었다. Javadoc에 적힌 이유는 count 쿼리를 모든 경우에 올바르게 만들 수 없다는 것이다. JPA에서는 group by 표현식이 여러 개이거나 having 절이 있는 쿼리의 count 쿼리를 생성할 수 없다고 명시되어 있다(Querydsl 5.0.0 Fetchable.java). 대안은 내용 조회는 fetch()로, 전체 개수는 select(member.count()) 형태의 별도 쿼리와 fetchOne()으로 나누어 작성하는 것이다.

  • https://stackoverflow.com/questions/69073926/jpaquery-fetchresults-is-deprecated-how-should-i-replace-it
  • https://yjh5369.tistory.com/entry/querydsl-50-fetchResult-fetchCount-deprecated

Stream에서 Page 만들기

이미 메모리에 있는 리스트나 스트림을 Spring Data의 Page로 감싸야 할 때는 PageImpl에 현재 페이지 내용, Pageable, 전체 개수를 넘긴다. 현재 페이지 내용은 skip(pageable.getOffset())과 limit(pageable.getPageSize())로 잘라 낸다. 데이터가 많다면 전체를 메모리에 올리는 대신 DB에서 페이지 단위로 조회하는 것이 맞다.

  • https://stackoverflow.com/questions/52930327/map-stream-of-data-into-paget

Validation

회원 가입에서 중복 사용자 검증을 다룬 글이다. 애플리케이션에서 “존재 여부 조회 후 저장”으로 검사하면 동시에 들어온 두 요청이 모두 검사를 통과할 수 있으므로, 최종 방어선은 DB의 유니크 제약이고 애플리케이션은 제약 위반 예외를 사용자에게 의미 있는 에러로 바꿔 주는 역할을 한다.

  • https://velog.io/@junhok82/troubleshootingDuplicatedUser

Swagger Error

Swagger 설정 중 만난 에러와 해결 과정을 기록한 글이다. Swagger UI 문서 생성 라이브러리는 Spring Boot 버전과 맞물려 동작하므로, 에러가 나면 두 의존성의 호환 여부를 먼저 확인하는 것이 순서다.

  • https://jazzodevlab.tistory.com/72

DTO 사용 시기

엔티티를 API 응답으로 그대로 내보내면 엔티티 필드 변경이 곧 API 스펙 변경이 되고, 지연 로딩 프록시나 양방향 연관관계 때문에 직렬화 문제가 생긴다. 그래서 계층 경계, 특히 컨트롤러의 요청·응답에는 DTO를 두는 것이 일반적인 권장이다. 서비스 계층 안에서까지 DTO로 바꿀지는 팀의 선택으로 남는다.

  • https://www.inflearn.com/questions/139564

Builder

빌더 패턴은 생성자 파라미터가 많을 때 어떤 값이 어떤 필드인지 이름으로 드러내며 객체를 만드는 방법이다. 링크 글은 인자가 많아도 순서와 상관없이 안전하게 만들 수 있고 이름으로 책임을 드러낼 수 있다는 장점을 들고, JPA 엔티티를 Builder로 생성할 때 필수 값이 빠지는 문제를 막으며 더 안전하게 쓰는 방법을 다룬다.

  • https://cheese10yun.github.io/spring-builder-pattern/

Setter in Entity

엔티티에 public setter를 열어 두면 어디서든 상태를 바꿀 수 있어서 변경 의도를 추적하기 어렵다. 링크의 답변들은 changeAddress(), cancel()처럼 의도가 드러나는 메서드로 상태를 바꾸고, 그 안에서 불변식을 검사하는 방식을 권한다.

  • https://www.inflearn.com/questions/16235
  • https://multifrontgarden.tistory.com/272

Entity Dirty Checking

JPA는 엔티티를 영속성 컨텍스트에 올릴 때 스냅샷을 남겨 두고, 플러시 시점에 현재 값과 비교해서 바뀐 엔티티에 대해 UPDATE를 생성한다. 이것이 변경 감지(dirty checking)다. 그래서 트랜잭션 안에서 조회한 엔티티의 값을 바꾸기만 하면 save()를 호출하지 않아도 커밋 시 반영된다. 반대로 준영속 상태의 엔티티는 변경 감지 대상이 아니다. Hibernate는 기본적으로 모든 컬럼을 포함한 UPDATE를 만들고, @DynamicUpdate를 붙이면 바뀐 컬럼만 포함한다.

  • https://jojoldu.tistory.com/536
  • https://jojoldu.tistory.com/415
참고한 자료외부 출처 2

외부 출처

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다