[우아한테크코스] 10월 19일 TIL
[Spring] lombok 주의할점
lombok은 개발자에게 극강의 편리함을 제공해주지만 주의해야할 점이 존재한다.
lombok의 toString() 메서드는 java Reflection을 통해 이루어진다.
1. AllArgsConstructor, RequiredArgsConstructor 사용 시
private int hi;
private int hello;
리팩터링 도중 hi와 hello의 순서를 바꾸면 lombok이 변수 순서를 바꾼다.
그렇게 되면 해당 생성자가 사용된 모든 곳에 순서를 바꿔줘야 하는데, 놓치기 매우 쉽다.
생성자는 builder를 사용할 것
2. @EqualsAndHashCode 사용 시
불변 객체가 아닌 경우에 사용하는 것이 문제가 된다. 필드 값이 변경되면 hashCode가 변경되면서 찾지 못하기 때문이다.
불변객체에만 사용하거나, @EqualsAndHashCode(of={"name"})
과 같이 동등성을 비교할 필드를 명시해야 한다.
따라서 @EqualsAndHashCode
와 @RequiredArgsConstructor
를 포함하는 @Data, @EqualsAndHashCode
와 @AllArgsConstructor
를 포함하는 @Value를 사용할 때도 주의해야만 한다.
3. @Builder를 생성자나 static 객체 생성 메서드에 사용하기
Builder를 사용하면 내부적으로 @AllArgsConstructor
를 포함하고 있다. package-private으로 생성하기 때문에 크게 문제가 되지 않지만 내부적으로 호출할 때 1번과 같은 문제가 발생할 수 있으니 어노테이션 자체를 생성자에 붙이거나 정적팩토리메서드에 붙이는 것이 좋다.
4. @NonNull 사용 시
테스트 커버리지 측정 시 브랜치 커버리지가 커버되지 않는다.
5. @toString, @EqualsAndHashCode 필드명 지정 시
of나 exclude 메서드를 이용해 파라미터로 특정 필드를 지정해 처리 대상에 포함하거나 제외할 수 있다.
하지만 이게 내부적으로 Reflection을 이용하기 때문에 필드명에 오타가 있어도 알기 어렵다.
뿐만 아니라, of나 exclude를 적절히 사용하지 않으면 상호 참조 중인 두 객체가 toString() 메서드를 호출하면 서로의 toString을 계속 호출하며 무한 루프에 빠질 수 있다.
결론
@Getter, @Setter, @ToString 정도만 사용하고 보수적으로 사용하도록 하자!
큰 프로젝트를 할 때 lombok.config
를 이용해 어노테이션을 금지할 수 있다.
[Spring] transaction
propagation이 REQUIRES_NEW인 경우에 서비스에서 트랜잭션(readonly=false) 메서드를 호출하고, 그 내부에서 또 다른 트랜잭션(readonly=true)을 호출하면 readonly=true의 옵션이 먹지 않는다.
그 이유는 트랜잭션은 우선 AOP로 동작하는데, AOP는 프록시로 한번 감싸진 채로 메서드를 동작시킨다. 하지만 그 내부에서 또 다른 트랜잭션이 발생하면 새로운 트랜잭션을 생성하기 위해 프록시를 갈아끼워야 한다. 따라서 requires_new 를 주었음에도 불구하고 새로운 트랜잭션이 생성되지 않고 기존의 트랜잭션을 사용한다.
[JPA] @DynamicInsert, @DynamicUpdate
DynamicInsert 어노테이션이 달려있는 엔티티의 경우 null이 들어오게 되면 쿼리에 해당 컬럼이 제외된다.
또한 update의 경우 변경된 필드만 수정하는 쿼리가 날라간다.
nullable=false인 경우는 null로 들어간다.
//전
Hibernate:
insert into member (name, age, college) values (?, ?, ?)
//후
Hibernate:
insert into member (name, age) values (?, ?)
Entity Listener
JPA의 리스너 기능은 엔티티의 생명주기에 따른 이벤트를 처리할 수 있게 한다.
PrePersist
: 엔티티를 영속성 컨텍트스에 관리하기 직전PostPersist
: 엔티티를 데이터베이스에 저장한 직후PreUpdate
: 엔티티를 데이터베이스에 수정하기 직전PostUpdate
: 엔티티를 데티터베이스에 수정한 직후PreRemove
: 엔티티를 영속성 컨텍스트에서 삭제하기 직전PostRemove
: 엔티티를 데이터베이스에 삭제한 직후PostLoad
: 엔티티가 영속성 선텍스트에 조회된 직후 또는 refresh를 호출한 후
[infra] 로드밸런싱 세션 문제
우리는 스케일 아웃을 통해 하나의 서버에 가는 부하를 줄이고자 한다. 이렇게 서버가 다수가 되어버리면, 요청이 들어왔을 때 어떠한 서버에 요청을 전달할지 결정해야하고, 이를 위해 사용하는 것이 로드밸런싱이다. 로드밸런싱은 병목현상이 생기지 않도록 적절한 서버에 요청을 전달하는 것이 목표이다.
그렇다면 서버의 데이터 정합성 문제는 어떻게 해결할 수 있을까? 제일 눈에 띄는 예시로 세션 관리 문제가 있다. 서버는 세션을 저장해 관리한다. 하지만 예를 들어 사용자는 로그인을 했는데 그에 대한 세션이 서버1에만 저장이 되어 있으면 어떻게 될까? 다른 어떠한 요청을 보냈을 때 서버2로 요청이 전달된다면 서버는 사용자가 로그인을 제대로 했는지 알 수 없을 것이다.
이 문제는 아래 방법들로 해결할 수 있다.
-
sticky session
사용자가 로그인을 해 세션이 저장되었다면, 그 사용자의 모든 요청은 저장된 서버로만 전송되는 방식이다.
로드밸런서는 요청을 받으면 요청에 쿠키가 존재하는지 확인하고, 있으면 쿠키에 지정된 서버로 전송하고 없으면 기존 알고리즘으로 서버를 선정해 요청을 전송한다.
이 경우 유저는 동일한 서버를 사용하기 때문에 데이터 정합성에서 자유롭다.
하지만 이렇게 되면 특정 서버에 트래픽이 집중될 가능성이 존재한다. 스케일 아웃의 장점을 누릴 수 없고 그렇게 하나의 서버가 장애가 발생하면 그 서버를 이용했던 사용자들은 세션 정보를 잃어버려 가용성이 떨어진다.
-
session clustering
여러 서버를 연결해 하나의 시스템처럼 동작하도록 하는것이 클러스터링
tomcat의 세션 클러스터링은?
All-to-all session replication을 이용해 세션을 복제한다. 세션 저장소에 요소가 변경되면 이 사항을 다른 모든 세션에 복제한다는 것이다. 하지만 이렇게 되면 모든 서버가 세션 객체를 가져야 하기 때문에 메모리가 많이 필요하고 서버가 늘어날수록 비효율적이다.
-
그외
spring에서는 bean을
globalSession
scope으로 등록하면 session이 전역 portlet의 생명주기의 scope를 가진다.Primary-secondary 세션 복제를 이용해 primary 서버는 secondary 서버에 세션 객체의 key-value 전체를 복제하고 그 외 서버에는 key만 복제하도록 한다. 그러면 복제에 대한 시간은 줄일 수 있지만 다시 value를 복제하는 단점이 여전히 존재한다.
session 저장소를 이용하는 세션 스토리지 방식, 여기서 in-memory db(ex. redis)를 쓰면 i/o에 대한 부담을 줄일 수 있고 없어지는 것도 세션이라 괜찮으니 disk 보다 낫다.