Post

4D4cat) 이 어노테이션은 왜 쓰는걸까?

4D4cat) 이 어노테이션은 왜 쓰는걸까?

🔔 어노테이션 종류와 용도

RequiredArgsConstructor

Lombok 라이브러리에서 제공하는 어노테이션으로, 클래스 내의 final 필드나 @NonNull 어노테이션이 붙은 필드들을 초기화하는 생성자를 자동으로 생성해준다. 보일러 플레이트 코드를 줄여준다는 것에 이점이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;

    // Lombok이 아래와 같은 생성자를 자동으로 생성해줍니다.
    // public UserService(UserRepository userRepository, EmailService emailService) {
    //     this.userRepository = userRepository;
    //     this.emailService = emailService;
    // }

    // 서비스 로직...
}

Autowired와 차이점

실제로 기능은 동일하지만, 코드량의 차이가 있다.

  1. 생성자 주입
    private final로 선언을 했더라도 생성자를 만들고 @Autowired를 설정해주기 때문에 불필요한 코드 증가

  2. 필드 주입
    필드 주입을 하게 되면 final로 선언이 불가하여 의존성이 변경될 수도 있다.

Transactional

Spring 프레임워크에서 제공하는 어노테이션으로, 메서드 또는 클래스 수준에서 트랜잭션의 경계를 정의한다.

  • 데이터 무결성 보장
    여러 데이터베이스 작업이 하나의 트랜잭션으로 묶여 원자성을 유지

  • 롤백 관리
    예외 발생 시 자동으로 트랜잭션을 롤백하여 데이터의 일관성을 유지

  • 트랜잭션 전파
    메서드 호출 간 트랜잭션의 전파 방식을 설정 가능

주요 속성

  • propagation: 트랜잭션 전파 방식을 정의
    • REQUIRED(Default)
      현재 트랜잭션이 존재하면 해당 트랜잭션을 사용하고 없으면 새로운 트랜잭션을 시작, 일관성 있는 트랜잭션 관리가 필요할 때 적합
    • REQUIRES_NEW
      현재 트랜잭션이 존재하면 일시 중단하고 새로운 트랜잭션을 시작, 기존 트랜잭션과 독립적으로 동작해야 하는 작업(Log, Audit 등)
    • SUPPORTS
      현재 트랜잭션이 존재하면 해당 트랜잭션을 사용하고, 없으면 트랜잭션 없이 실행, 트랜잭션에 의존하지 않는 읽기 작업
    • MANDATORY
      반드시 기존 트랜잭션 내에서 실행되어야 한다. 트랜잭션이 없으면 예외 발생, 반드시 트랜잭션 내에서 실행되어야 하는 중요 작업
  • isolation: 트랜잭션의 격리 수준을 설정
    • READ_COMMITTED: 커밋된 데이터만 읽을 수 있다. 더티 리드를 방지
    • SERIALIZABLE: 가장 높은 격리 수준. 트랜잭션이 순차적으로 실행되는 것처럼 동작하여 모든 동시성 문제를 방지
  • timeout: 트랜잭션이 타임아웃되기까지의 시간을 설정. 설정된 시간이 초과되면 트랜잭션이 롤백

  • readOnly: 트랜잭션이 읽기 전용인지 설정

일반 사례

  • 기본 트랜잭션: @Transactional만 사용하거나 propagation과 isolation을 기본값으로 설정
  • 읽기 전용 작업: @Transactional(readOnly = true)
  • 독립적인 트랜잭션 필요 시: @Transactional(propagation = Propagation.REQUIRES_NEW)
  • 트랜잭션 타임아웃 설정: @Transactional(timeout = 30)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final PaymentService paymentService;

    // 생성자 주입 (예제 간결화를 위해 생략)

    @Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.READ_COMMITTED,
        timeout = 60,
        readOnly = false,
        rollbackFor = { PaymentException.class, OrderException.class },
        noRollbackFor = { ValidationException.class }
    )
    public void placeOrder(Order order) throws PaymentException, OrderException, ValidationException {
        orderRepository.save(order);
        paymentService.processPayment(order.getPaymentDetails());
        // ...
    }
}

MappedSuperclass

JPA에서 제공하는 어노테이션으로, 공통 매핑 정보를 상속받기 위한 슈퍼클래스를 정의할 때 사용한다. @Entity로 선언되지 않은 클래스에 적용되며, 이를 상속받는 엔티티 클래스들이 상속받은 필드들을 매핑할 수 있다.

@MappedSuperclass를 상속받는 클래스는 @Entity가 아니므로, 데이터베이스 테이블에 직접 매핑되지 않지만 상속받는 @Entity 클래스들은 @MappedSuperclass의 필드를 매핑합니다.

Abstract Class?

공통 필드를 정의하는 역할만 하므로, 직접 인스턴스를 생성할 필요가 없다. 추상 클래스로 정의하면서 설계의 명확성과 인스턴스화 방지할 수 있다.

EntityListeners

엔티티의 라이프사이클 이벤트(예: @PrePersist, @PostLoad 등)를 처리하기 위한 리스너 클래스를 지정할 때 사용한다. 엔티티가 생성, 업데이트, 삭제될 때 특정 로직을 자동으로 실행할 수 있다. 감사(Audit) 기능 (생성 시간, 수정 시간, 생성자, 수정자 등)의 필드를 자동으로 관리할 때 유용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class AuditListener {

    @PrePersist
    public void prePersist(Object target) {
        if (target instanceof BaseEntity) {
            BaseEntity entity = (BaseEntity) target;
            entity.setCreatedAt(LocalDateTime.now());
            entity.setUpdatedAt(LocalDateTime.now());
        }
    }

    @PreUpdate
    public void preUpdate(Object target) {
        if (target instanceof BaseEntity) {
            BaseEntity entity = (BaseEntity) target;
            entity.setUpdatedAt(LocalDateTime.now());
        }
    }
}

@MappedSuperclass
@EntityListeners(AuditListener.class)
public abstract class BaseEntity {

    // 필드 정의
    private Long id;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    // getters and setters
}

@Entity
@Table(name = "products")
public class Product extends BaseEntity {
    private String name;
    private Double price;

    // getters and setters
}

자료 출처

ChatGPT

This post is licensed under CC BY 4.0 by the author.