포스트

2022-03-08-TIL

Today I Learned

@OneToOne with @PrimaryKeyJoinColumn vs @MapsId

일대일 관계를 매핑하는 방법은 외래 키 컬럼을 두는 방법, 조인 테이블을 쓰는 방법, 그리고 자식 테이블의 기본 키를 부모의 기본 키와 공유하는 방법이 있다. 마지막 방법(shared primary key)을 JPA에서 표현할 때 @PrimaryKeyJoinColumn과 @MapsId 두 가지를 쓸 수 있는데, 동작이 미묘하게 다르다.

Hibernate 개발 메일링 리스트의 글은 차이를 이렇게 정리한다.

항목@PrimaryKeyJoinColumn@MapsId
자식 ID 할당애플리케이션이 직접 넣어야 한다부모 ID에서 자동으로 채운다
외래 키 생성optional = false일 때만optional과 관계없이 생성
부모가 없는 행 조회부모를 null로 로드ObjectNotFoundException

같은 스레드에서 Vlad Mihalcea는 @MapsId 쪽 동작이 맞다고 답한다. 새로 매핑한다면 ID를 자동으로 맞춰 주는 @MapsId가 실수가 적다.

1
2
3
4
5
6
7
8
9
10
@Entity
public class MemberDetail {
    @Id
    private Long id;

    @MapsId
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;
}
  • https://www.baeldung.com/jpa-one-to-one
  • https://lists.jboss.org/pipermail/hibernate-dev/2018-September/018008.html
  • https://www.baeldung.com/jpa-one-to-one

@Transactional 주의할 점

링크된 글은 테스트 코드에 붙인 @Transactional이 문제를 가린다는 점을 다룬다. 테스트 메서드 전체가 트랜잭션 안에서 실행되면 영속성 컨텍스트가 계속 살아 있어 지연 로딩이 성공한다. 그래서 서비스 메서드에 @Transactional을 빠뜨려도 테스트는 통과하고, 운영에서만 LazyInitializationException이 난다. 테스트 후 롤백이 편하다는 장점과 이런 거짓 양성 사이에서 선택해야 한다.

프로덕션 코드에서도 Spring 문서가 짚는 함정이 있다. @Transactional은 프록시 기반이라 같은 클래스 안에서 자기 메서드를 호출하면(self-invocation) 트랜잭션이 적용되지 않는다. 또 기본적으로 unchecked 예외(RuntimeException, Error)에서만 롤백하고 checked 예외에서는 커밋하므로, 필요하면 rollbackFor를 지정해야 한다.

  • https://javabom.tistory.com/103

자바 바이트코드

자바 소스는 javac가 바이트코드(.class)로 컴파일하고, JVM이 이를 해석하거나 JIT 컴파일해서 실행한다. 바이트코드는 스택 기반 명령어 집합이라 iload, invokevirtual 같은 명령이 피연산자 스택에 값을 넣고 꺼내며 동작한다. javap -c로 클래스 파일을 디스어셈블해 보면 문자열 연결, 오토박싱, 람다가 실제로 어떤 명령으로 바뀌는지 확인할 수 있다.

  • https://d2.naver.com/helloworld/1230

Multiple Microservices in Single Repository

여러 마이크로서비스를 하나의 저장소(모노레포)에 둘지, 서비스마다 저장소를 나눌지(폴리레포)의 문제다. 모노레포는 공통 코드 공유와 여러 서비스에 걸친 변경을 한 커밋으로 처리하기 쉽지만, 저장소가 커질수록 빌드와 CI에서 변경된 서비스만 골라 빌드하는 장치가 필요하다. 폴리레포는 서비스별 독립 배포와 권한 분리가 자연스럽지만, 공통 라이브러리 버전 관리와 서비스 간 변경 조율이 번거롭다.

  • https://stackoverflow.com/questions/58770235/multiple-microservices-in-one-repository
  • https://levelup.gitconnected.com/how-many-repositories-do-you-need-for-a-microservices-project-b5c991aa440
  • https://circleci.com/blog/monorepo-dev-practices/

Git-flow

develop에서 feature 브랜치를 따서 개발하고, 배포 준비가 되면 release 브랜치에서 QA 후 master(main)와 develop에 병합하며, 운영 장애는 master에서 딴 hotfix로 고치는 브랜치 전략이다. 우아한형제들 기술 블로그 글은 이 전략을 팀에서 운영하는 방식을 소개한다.

  • https://techblog.woowahan.com/2553/

Harbor 배포

Harbor는 정책과 역할 기반 접근 제어, 이미지 취약점 스캔, 서명을 제공하는 오픈소스 컨테이너 레지스트리이고, CNCF Graduated 프로젝트다. Docker Hub 대신 사내망에 프라이빗 레지스트리를 두고 싶을 때 쓴다. 설치는 공식 설치 스크립트와 Docker Compose로 하거나, 쿠버네티스에는 Helm 차트로 배포한다.

  • https://goharbor.io/
  • https://happyer16.tistory.com/entry/Harbor%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80
  • https://wookiist.dev/126

Jar vs War

JAR는 클래스 파일과 리소스를 묶은 자바 아카이브이고, WAR는 서블릿 컨테이너에 배포하기 위한 웹 애플리케이션 아카이브로 WEB-INF/web.xml, WEB-INF/classes, WEB-INF/lib 같은 정해진 구조를 가진다. Spring Boot는 내장 톰캣을 포함한 실행 가능한 JAR(java -jar)를 기본으로 만들고, 외부 톰캣에 배포해야 할 때만 WAR로 패키징한다.

  • https://ifuwanna.tistory.com/224

Swagger

@ApiImplicitParam은 swagger-core 1.x(springfox에서 사용) 애너테이션으로, 메서드 파라미터와 직접 연결되지 않은 파라미터를 문서에 기술할 때 쓴다. 예를 들어 서블릿 요청에서 직접 꺼내는 헤더나 쿼리 파라미터를 name, paramType, dataType, required로 설명하고, 여러 개면 @ApiImplicitParams로 묶는다. 이런 애너테이션이 컨트롤러에 쌓이면 코드가 길어지는 것이 Swagger의 대표적인 단점으로 꼽힌다.

  • https://docs.swagger.io/swagger-core/apidocs/com/wordnik/swagger/annotations/ApiImplicitParam.html
  • https://yookeun.github.io/java/2017/02/26/java-swagger/
참고한 자료외부 출처 2

외부 출처

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

댓글

아직 댓글이 없습니다