2023-05-02-TIL
2023-05-02-TIL
Today I Learned
IdentifierGenerator
IdentifierGenerator는 Hibernate가 엔티티의 식별자(ID)를 만들 때 쓰는 계약이다. Javadoc은 이 인터페이스를 고유 식별자를 생성하는 클래스와 Session 사이의 일반 계약이라고 설명한다(IdentifierGenerator 3.5 Javadoc). IDENTITY나 SEQUENCE 같은 기본 전략으로 부족할 때, 예를 들어 접두사와 날짜를 붙인 주문번호를 ID로 쓰고 싶을 때 이 인터페이스를 구현한다. Hibernate 사용자 가이드는 @GenericGenerator로 기본 제공 생성기뿐 아니라 직접 만든 IdentifierGenerator 구현도 연결할 수 있다고 설명한다(Hibernate User Guide 5.6: Using @GenericGenerator).
1
2
3
4
@Id
@GeneratedValue(generator = "order-no")
@GenericGenerator(name = "order-no", strategy = "com.example.OrderNoGenerator")
private String id;
직접 만든 생성기는 동시에 여러 요청이 들어와도 값이 겹치지 않아야 한다는 책임까지 함께 진다. 그래서 내부에서 DB 시퀀스 같은 원자적인 채번 수단을 함께 쓰는 편이 안전하다.
- https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/id/IdentifierGenerator.html
- https://zet-it-story.tistory.com/1
- https://www.baeldung.com/hibernate-identifiers
- https://kapentaz.github.io/jpa/Hibernate%EC%97%90%EC%84%9C-Custom-ID-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0/#
Canonical Name
Canonical Name은 DNS의 CNAME 레코드에서 말하는 “정식 이름”이다. RFC 1034는 CNAME 레코드가 자기 이름이 별칭(alias)임을 나타내고, 대응하는 정식 이름을 데이터로 가진다고 정의한다. 또 어떤 이름에 CNAME이 있으면 그 이름에는 다른 데이터가 없어야 한다고 정한다. 그래야 정식 이름과 별칭의 데이터가 달라질 수 없기 때문이다(RFC 1034, 3.6.2).
예를 들어 img.example.com을 CDN이나 S3 버킷의 도메인에 CNAME으로 연결하면, 사용자는 우리 도메인으로 접근하지만 실제 이름 해석은 정식 이름을 따라간다. 같은 규칙 때문에 SOA, NS 레코드가 이미 있는 루트 도메인(zone apex)에는 CNAME을 둘 수 없다.
- https://www.techtarget.com/searchwindowsserver/definition/canonical-name
Uploading Image with Redis TTL
글 작성 화면에서 이미지를 먼저 업로드하고 글은 나중에 저장하는 구조에서는, 업로드만 하고 글을 저장하지 않은 이미지가 남는다. 이를 정리하는 방법으로 Redis의 만료 시간을 쓸 수 있다. 업로드할 때 이미지 키를 만료 시간과 함께 저장해 두고, 글이 저장되면 PERSIST로 만료를 없애거나 키를 지운다. 만료된 키는 Redis가 자동으로 삭제한다(EXPIRE).
1
2
SET upload:tmp:abc123 "s3://bucket/tmp/abc123.png" EX 3600
TTL upload:tmp:abc123 # 남은 초. 만료가 없으면 -1, 키가 없으면 -2
TTL은 남은 시간을 초 단위로 돌려준다. Redis 2.8부터 키가 없으면 -2, 키는 있지만 만료가 없으면 -1을 돌려준다(TTL). 주의할 점은 Redis 키가 사라져도 저장소의 실제 파일은 지워지지 않는다는 것이다. 키는 “아직 확정되지 않은 업로드”라는 표시일 뿐이므로, 실제 파일 삭제는 만료 이벤트를 구독하거나 주기적인 정리 작업으로 따로 해야 한다.
- https://redis.io/commands/ttl/
Swagger with Separated Configuration File
Spring Boot에서 OpenAPI 문서를 만드는 springdoc-openapi는 실행 중인 애플리케이션의 설정과 애노테이션을 읽어 문서를 자동으로 만든다. 문서 제목, 버전, 보안 스키마 같은 공통 정보는 @Configuration 클래스에 OpenAPI 빈으로 따로 정의하고, API를 묶어 보여 주고 싶으면 GroupedOpenApi 빈을 추가한다(springdoc-openapi).
1
2
3
4
5
6
7
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI openAPI() {
return new OpenAPI().info(new Info().title("My API").version("1.0"));
}
}
설명과 예시 값이 컨트롤러 애노테이션에 길게 붙으면 비즈니스 코드보다 문서 코드가 더 많아진다. 공통 설정을 설정 클래스로 분리해 두면 컨트롤러에는 엔드포인트별 설명만 남는다.
- https://www.baeldung.com/swagger-set-example-description
org.hibernate.TransientObjectException: object references an unsaved transient instance
이 예외는 영속 상태 객체를 기대하는 Session 메서드에 transient 객체를 넘겼을 때 발생한다(TransientObjectException Javadoc). transient는 막 생성되어 영속성 컨텍스트와 연결되지 않았고 DB에도 없는 상태다(Hibernate User Guide 5.6: Persistence Context).
흔한 상황은 아직 저장하지 않은 엔티티를 연관관계로 참조한 채 부모를 저장할 때다. 플러시 시점에 Hibernate는 외래 키 값을 채워야 하는데 참조 대상이 DB에 없으니 예외가 난다. 참조 대상을 먼저 persist하거나, 부모와 생명주기를 같이하는 관계라면 cascade = CascadeType.PERSIST를 설정해 함께 저장되게 하면 된다.
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:
지연 로딩(LAZY) 연관관계는 실제로 접근할 때 쿼리를 날려 초기화한다. 그런데 영속성 컨텍스트가 이미 닫힌 뒤, 예를 들어 트랜잭션이 끝난 컨트롤러나 뷰에서 컬렉션에 접근하면 초기화할 수 없어 이 예외가 난다. Hibernate 문서는 LAZY 연관관계는 접근 전에 초기화해야 하며, 가장 좋은 해결책은 영속성 컨텍스트를 닫기 전에 필요한 연관관계를 모두 가져오는 것이라고 말한다. JOIN FETCH가 대표적인 방법이다(Hibernate User Guide 5.6: Fetching associations).
hibernate.enable_lazy_load_no_trans를 켜면 예외는 사라지지만, 문서는 필요한 속성을 세션 종료 전에 초기화하는 fetch 계획을 쓰는 편이 낫고 이 설정은 켜지 않는 게 좋다고 적고 있다.
댓글
아직 댓글이 없습니다