TIL(Today I Learned)

[Spring] 스프링에서 알아두면 좋은 어노테이션(Annotation) 모음

yunseohhe 2024. 10. 26. 22:44

1. Validation 기능

  • 개념
     - 사용자 입력값의 유효성을 검증하여 잘못된 데이터 입력을 방지하는 기능이다.
  • 적용 사례
     - 프론트엔드와 백엔드 모두에서 검증을 수행하여 안전한 데이터 처리가 가능하며, 유효성 검증 실패 시 예외 처리를 통해 사용자에게 명확한 오류 메시지를 제공할 수 있다.
  • 주요 어노테이션
     - 밑 다양한 어노테이션을 사용해 필드의 유효성을 정의할 수 있다.
@NotNull, @Size, @Valid, @Validated, @ControllerAdvice ...

 

  • DTO 클래스에서 사용하는 Validation 어노테이션
     - @NotNull: 필수 입력 필드로, 값이 null이 아니어야 함을 보장한다.
     - @Size: 문자열이나 컬렉션의 길이를 제한한다.
     - @Email: 유효한 이메일 형식인지 확인한다.
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public class UserDTO {

    @NotNull(message = "이름은 필수 입력 항목입니다.")
    private String name;

    @Size(min = 10, max = 15, message = "전화번호는 10자 이상 15자 이하여야 합니다.")
    private String phoneNumber;

    @Email(message = "유효한 이메일 주소여야 합니다.")
    private String email;
}

 

  • 컨트롤러에서 사용하는 @Valid 또는 @Validated
     - DTO의 유효성 검사가 필요한 메서드 파라미터에 @Valid를 적용하여, DTO 클래스에 설정된 모든 유효성 검사를 실행한다.
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;

@RestController
@RequestMapping("/api/users")
public class UserController {

    @PostMapping("/create")
    public ResponseEntity<String> createUser(@Valid @RequestBody UserDTO userDTO) {
        // 유효성 검증 성공 시 로직 처리
        return ResponseEntity.ok("User created successfully");
    }
}

 

 cf ) @Valid랑 @Validated의 차이

  @Valid @Validated
제공 자바 표준(javax.validation) 스프링(org.springframework.validation)
주 용도 일반적인 유효성 검사 유효성 검사 그룹 지정 가
중첩 객체 검사 가능 가능
유효성 그룹 지정 불가능 가능 (@Validated(그룹명.class))
  • 예외 처리(Optional)
     - 검증이 실패할 경우 @ControllerAdvice@ExceptionHandler로 예외 처리를 커스터마이징할 수 있다.
     - @ControllerAdvice : 전역 예외 처리를 위한 클래스에 붙이는 어노테이션이고, 프로젝트 전체에서 발생하는 예외를 한곳에서 처리할 수 있게 해준다.
     - @ExceptionHandler(MethodArgumentNotValidException.class): @Valid를 통해 검증이 실패했을 때 발생하는 MethodArgumentNotValidException 예외를 처리한다.(밑의 코드 예시)
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class GlobalExceptionHandler {

    // 유효성 검증 실패 시 발생하는 예외 처리
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        
        // 발생한 모든 필드 오류를 순회하면서 필드 이름과 오류 메시지를 저장
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors);
    }
}

 

 

cf ) 글로벌 익셉션 핸들러는 "@ControllerAdvice"를 통해 구현되며, 전역적으로 모든 컨트롤러의 예외를 한곳에서 처리하는 역할을 하는 것이다.

 

cf ) @ControllerAdvice와 @RestControllerAdvice의 차이

어노테이션 주요 용도 반환 형
@ControllerAdvice MVC 애플리케이션 HTML, 뷰 이름
@RestControllerAdvice REST API 애플리케이션 (JSON/XML) JSON 또는 XML

 

 

2. Profile(프로파일) 기능

  • 개념
     - 프로파일 관리 : @Profile을 사용하여 개발, 테스트, 프로덕션 등 다양한 환경에 맞춰 설정을 분리할 수 있습니다.
  • 적용 사례
     - 환경에 맞는 빈을 로드하여 특정 환경에 적합한 설정을 적용할 수 있으며, 실무에서 환경별 설정 분리를 통해 시스템 안정성과 유지보수성을 높일 수 있습니다.
  • 설정 방법
     - 환경별 설정 파일을 resurces(리소스)폴더 밑에 application-dev.yml, application-prod.yml과 같이 별도로 생성해 관리합니다.
  • 코드 예시
@Configuration
@Profile("dev")
public class DevDataSourceConfig {
    @Bean
    public DataSource dataSource() {
        // 개발 환경에 맞는 DataSource 설정
    }
}

@Configuration
@Profile("prod")
public class ProdDataSourceConfig {
    @Bean
    public DataSource dataSource() {
        // 프로덕션 환경에 맞는 DataSource 설정
    }
}

 

 

cf ) 물론, 어노테이션으로 설정안하고 IntelliJ Run/Debug Configurations설정에서 등록하면 해당하는 프로파일에 맞는 설정 파일을 적용할 수도 있다.
 - 밑의 "Active profiles"에서 설정하고 싶은 프로파일을 등록하면 된다.

 

@Profile 어노테이션 설정 vs IntelliJ 설정 차이

  • @Profile 어노테이션은 위의 코드 예시처럼 환경에 따라 다른 빈을 활성화해야 할 때 유용하다.
  • 프로파일을 인텔리제이 설정에서 등록하면 해당 프로파일에 맞는 설정 파일을 적용할 수 있다.

 

3. Event(이벤트) 기능

  • 개념
     - 이벤트 기능: ApplicationEvent@EventListener를 사용해 특정 이벤트 발생 시 자동으로 관련 작업을 수행할 수 있다.
  • 적용 사례
     - 이벤트 기반 설계를 통해 모듈 간 결합도를 낮추고, 비동기 작업으로 성능을 최적화할 수 있습니다.
     - 이벤트가 발생하면 리스너가 반응하는 구조이다.
     - 예를 들어,  "회원 가입 시 축하 이메일 전송"과 같은 작업을 비동기로 처리하여 애플리케이션의 반응성을 개선할 수 있습니다.
  • 코드 예시
// 이벤트 클래스: 회원 가입 시 발생하는 이벤트
public class UserRegisteredEvent extends ApplicationEvent {
    public UserRegisteredEvent(Object source) {
        super(source);
    }
}

// 이벤트 리스너: 회원 가입 이벤트 발생 시 실행되는 메서드
@EventListener
public void handleUserRegistered(UserRegisteredEvent event) {
    // 예를 들어, 회원 가입 축하 이메일을 보내는 로직
    System.out.println("회원 가입 축하 이메일을 전송합니다.");
}

 

 

4. Scheduled 작업

  • 개념
     - 스케줄링 작업: @Scheduled 어노테이션을 사용해 특정 작업을 일정한 시간 간격으로 자동으로 실행할 수 있다.
  • 적용 사례
     - 주기적으로 실행해야 하는 작업(예: 백업, 정기 보고서 전송)을 자동화할 수 있다.
     - 실무에서는 다양한 스케줄링 작업을 통해 시스템 운영을 효율적으로 관리할 수 있다.
  • 시간 설정 방법
     - fixedDelay, fixedRate, cron 표현식을 사용하여 시간 간격을 설정한다.
  • 코드 예시
@Scheduled(cron = "0 0 0 * * ?")
public void backupDatabase() {
    // 매일 자정에 데이터 백업
}

 

cf ) fixedDelay 표현식

  • 이전 작업이 끝난 후 일정 시간이 지난 뒤 다음 작업을 실행한다.
  • 사용법
     : @Scheduled(fixedDelay = 5000) == 이전 작업이 끝난 후 5초 뒤에 다음 작업을 시작한다.
@Scheduled(fixedDelay = 5000)
public void taskWithFixedDelay() {
    System.out.println("5초 간격으로 실행 (이전 작업 종료 후 대기)");
}

 

cf ) fixedRate 표현식

  • 이전 작업이 끝나기를 기다리지 않고 고정된 시간 간격으로 작업을 시작한다.
     -  fixedRate는 이전 작업이 끝나지 않았더라도 새로운 작업을 시작할 수 있기 때문에, 작업이 겹칠 가능성이 있다.
  • 사용법
     : @Scheduled(fixedRate = 5000) == 5초마다 작업을 실행한다.
@Scheduled(fixedRate = 5000)
public void taskWithFixedRate() {
    System.out.println("5초마다 실행 (이전 작업 종료 기다리지 않음)");
}

 

cf ) initialDelay 표현식

  • 작업 시작 전 대기 시간을 설정한다.
      - 주로 fixedRate나 fixedDelay와 함께 사용하여, 처음 작업이 시작되기 전에 대기 시간을 지정할 때 유용하다.
  • 사용법
     :  @Scheduled(fixedRate = 5000, initialDelay = 2000) == 애플리케이션 시작 후 2초 대기 후, 5초마다 작업을 실행한다.
@Scheduled(fixedRate = 5000, initialDelay = 2000)
public void taskWithInitialDelay() {
    System.out.println("처음 2초 대기 후, 5초마다 실행");
}

 

cf ) cron 표현식

  • 복잡한 시간 간격을 설정할 때 사용하는 표현식이다. 초, 분, 시, 일, 월, 요일 단위로 작업을 설정할 수 있습니다.
  • 사용법
     :  @Scheduled(cron = "0 0 0 * * ?") == 매일 자정에 작업을 실행합니다.
필드 의미 예시
0~59 0
0~59 30
0~23 10
1~31 *(매일)
1~12 *(매월)
요일 0~7(일요일) ?(특정하지 않음)
@Scheduled(cron = "0 0 0 * * ?")
public void taskWithCron() {
    System.out.println("매일 자정에 실행");
}

 

 

5. Cache (캐싱)

  • 개념
     - 캐싱: @Cacheable, @CacheEvict 등을 통해 자주 사용되는 데이터를 메모리에 캐싱하여 성능을 최적화하는 기능이다.
  • 적용 사례
     - 자주 조회되는 데이터를 캐싱하여 데이터베이스와 외부 API 호출 빈도를 줄여 성능을 최적화할 수 있다.
     - 캐싱은 트래픽이 많은 환경에서 응답 시간을 단축하는 데 매우 유용하다.
  • 코드 예시(@Cacheable)
     - 역할 : 메서드의 결과를 캐시에 저장해 두었다가, 이후 동일한 요청이 오면 저장된 결과를 바로 반환한다.
     - 사용 방법: 데이터베이스나 API에서 자주 조회하는 데이터를 캐싱하여, 다음번 호출 시 캐시에서 가져올 수 있도록 한다.
     - 밑의 예시 설명 : getUserById 메서드가 처음 호출될 때, users라는 캐시 이름으로 결과를 저장합니다. 이후 동일한 id로 호출되면 데이터베이스를 다시 조회하지 않고 캐시에 저장된 결과를 반환한다.
@Cacheable("users")
public User getUserById(Long id) {
    // 데이터베이스에서 사용자 정보를 조회하는 코드
    System.out.println("DB 조회 중..."); // 실제 캐싱이 동작하면 이 출력은 한 번만 됩니다.
    return userRepository.findById(id).orElse(null);
}
  • 코드 예시(@CacheEvict)
     - 역할 : 캐시에 저장된 데이터를 삭제한다. 주로 데이터가 변경될 때 사용한다.
     - 사용 방법 : 새로운 데이터를 추가하거나 기존 데이터를 수정할 때, 이전에 캐싱된 데이터가 최신 상태가 아니므로 캐시를 삭제한다.
     - 밑의 예시 설명 : updateUser 메서드가 호출될 때, users 캐시에 저장된 특정 id의 데이터를 삭제한다. 다음번 getUserById 호출 시에는 다시 데이터베이스에서 조회하고, 최신 정보로 캐싱한다.
@CacheEvict(value = "users", key = "#id")
public void updateUser(Long id, User user) {
    // 사용자 정보 업데이트 코드
    userRepository.save(user);
}