Spring - annotations

Daniel6364·2022년 8월 9일
0

Annotation Lists

  • @CrossOrigin ✅
  • @PutMapping
  • @PathVariable
  • @RequestBody
  • @Valid ✅
  • @Transactional ✅
  • @Before ✅
  • @MappedSuperclass ✅
  • @CreatedDate ✅
  • @LastModifiedDate ✅
  • @JsonIgnore ✅
  • @GetMapping ✅
    • produces
    • consumes
  • @JsonInclude ✅
  • @RunWith(SpringRunner.class)
    → @ExtendWith(SpringExtension.class) ✅
  • @PostConstruct ✅
  • @RunWith(SpringRunner.class) ✅
  • @WebMvcTest / @AutoConfigureMockMvc ✅

@CrossOrigin

SOP (Same Origin Policy)

CORS에 대한 개념을 이해하기 전에 SOP이 무엇인지를 알아야합니다.

SOP란 같은 Origin에만 요청을 보낼 수 있게 제한하는 보안 정책을 의미합니다.

Origin은 아래와 같은 구성으로 이루어져 있습니다.

  • URI Schema(ex. http, https)
  • Hostname(ex. localhost, naver.com)
  • Port(ex. 80, 8080)

이 중에 하나라도 구성이 다르면 SOP 정책에 걸리기 때문에 ajax 요청을 보낼 수 없습니다.

CORS(Cross Origin Resource Sharing)

CORS를 한마디로 정의하면 서로 다른 Origin끼리 요청을 주고 받을 수 있게 정해둔 표준이라고 할 수 있습니다. Spring에서는 @CrossOrigin이라는 어노테이션을 지원해주기 때문에, 이 어노테이션을 사용하면 간단하게 CORS를 사용할 수 있습니다.

@Valid

DTO 객체에 정의된 제약 조건을 검증하도록 지시하는 어노테이션

@Transactional

@Transactional이 적용된 메소드가 호출될 경우, PlatformTransactionManager를 사용하여 트랜잭션을 시작하고, 정상 여부에 따라 Commit/Rollback 동작을 수행한다.

Transaction Options

  • isolation : 트랜잭션에서 일관성없는 데이터 허용 수준을 설정 (격리 수준)
  • propagation : 동작 도중 다른 트랜잭션을 호출할 때, 어떻게 할 것인지 지정 (전파 옵션)
  • noRollbackFor : 특정 예외 발생 시 rollback이 동작하지 않도록 설정
  • rollbackFor : 특정 예외 발생 시 rollback이 동작하도록 설정
  • timeout : 지정한 시간 내에 메소드 수행이 완료되지 않으면 rollback이 동작하도록 설정
  • readOnly : 트랜잭션을 읽기 전용으로 설정

1. isolation

concurrency side effects on a transaction

  • Dirty read : 동시 트랜잭션의 커밋되지 않은 변경 내용을 조회하는 상황 (데이터 불일치)
  • Nonrepeatable read : 동시 트랜잭션이 동일한 행을 업데이트하고 커밋하는 경우, 행을 다시 조회할 때 다른 값을 얻 는 상황
  • Phantom read : 다른 트랜잭션이 특정 범위의 행을 추가/제거할 경우, 커밋 전/후 조회 결과가 다른 상황
@Transactional(isolation = Isolation.XXX)
public void example(String message) {
    // ...
}
  • DEFAULT
    • DBMS의 기본 격리 수준 적용
  • READ_UNCOMMITED (level 0)
    • 트랜잭션의 동시 액세스 허용
    • 세 가지 동시성 부작용이 모두 발생 (Dirty read, Nonrepeatable read, Phantom read)
    • Postgres는 미지원(대신 READ_COMMITED 로 폴백), Oracle은 지원하거나 허용하지 않음
  • READ_COMMITED (level 1)
    • Dirty read 방지
    • 나머지 부작용은 여전히 발생할 수 있음 (Nonrepeatable read, Phantom read)
    • Postgres, SQL Server 및 Oracle의 기본 수준
  • REPEATEABLE_READ (level 2)
    • Dirty read, Nonrepeatable read 방지
    • 업데이트 손실을 방지하기 위해 필요한 가장 낮은 수준 (동시 액세스를 허용하지 않음)
    • Phantom read 부작용은 여전히 발생
    • MySQL의 기본 수준, Oracle은 미지원
  • SERIALIZABLE (level 3)
    • 가장 높은 격리 수준이지만, 동시 호출을 순차적으로 실행하므로 성능 저하의 우려
    • 모든 부작용을 방지

2. propagation

@Transactional(propagation = Propagation.XXX)
public void example(String user) { 
    // ... 
}
  • REQUIRED (default)
    • 활성 트랜잭션이 있는지 확인하고, 아무것도 없으면 새 트랜잭션을 생성
  • SUPPORTS
    • 활성 트랜잭션이 있는지 확인하고, 있으면 기존 트랜잭션 사용. 없으면 트랜잭션 없이 실행
  • MANDATORY
    • 활성 트랜잭션이 있으면 사용하고, 없으면 예외 발생
    • 독립적으로 트랜잭션을 진행하면 안 되는 경우 사용
  • NEVER
    • 활성 트랜잭션이 있으면 예외 발생
    • 트랜잭션을 사용하지 않도록 제어할 경우
  • NOT_SUPPORTED
    • 현재 트랜잭션이 존재하면 트랜잭션을 일시 중단한 다음 트랜잭션 없이 비즈니스 로직 실행
  • REQUIRES_NEW
    • 현재 트랜잭션이 존재하는 경우, 현재 트랜잭션을 일시 중단하고 새 트랜잭션을 생성
  • NESTED
    • 트랜잭션이 존재하는지 확인하고 존재하는 경우 저장점을 표시
    • 비즈니스 로직 실행에서 예외가 발생하면 트랜잭션이 이 저장 지점으로 롤백
    • 활성 트랜잭션이 없으면 REQUIRED 처럼 작동

3. rollbackFor, noRollbackFor

  • 선언적 트랜잭션에서는 런타임 예외가 발생하면 롤백 수행
    • 예외가 발생하지 않거나 체크 예외 발생 시 커밋
    • 스프링에서는 데이터 액세스 기술 예외는 런타임 예외로 던져지므로 런타임 예외만 롤백 대상으로 삼음
    • rollbackFor 옵션으로 기본 동작방식 변경 가능

rollbackFor

  • 특정 예외가 발생 시 강제로 Rollback
@Transactional(rollbackFor=Exception.class)

noRollbackFor

  • 특정 예외의 발생 시 Rollback 제외
 @Transactional(noRollbackFor=Exception.class)

4. timeout

  • 지정한 시간 내에 해당 메소드 수행이 완료되이 않은 경우 rollback 수행 (단위: second)
  • -1일 경우 no timeout (default : -1)
@Transactional(timeout=10)

5. readOnly

  • 트랜잭션을 읽기 전용으로 설정
  • 성능을 최적화하기 위해 사용하거나, 특정 트랜잭션 작업 안에서 쓰기 작업이 일어나는 것을 의도적으로 방지하기 위해 사용
  • readOnly = true인 경우 INSERT, UPDATE, DELETE 작업 진행 시 실행 시 예외 발생(default : false)
@Transactional(readOnly = true)

출처: https://data-make.tistory.com/738 [Data Makes Our Future:티스토리]


@Before

Spring 에서 테스트 코드를 작성할 때, 모든 테스트 코드 전에 반복적으로 해 주어야 하는 작업이 필요할 때가 있습니다.

예를 들어, 사용자 인증이 선행되어야 하는 테스트의 경우, 매 테스트 코드마다 인증하는 코드를 넣어야 합니다.

public class Test {
    @Test
    public void test1(){
        authenticateForTest();  // login
        System.out.println("test 1");
    }

    @Test
    public void test2(){
        authenticateForTest();  // login
        System.out.println("test 2");
    }

    private void authenticateForTest() {
        System.out.println("authenticate");
    }
}

JUnit 에서는 이런 반복적인 코드를 없애기 위해 @Before 어노테이션을 제공합니다.


@MappedSuperclass

import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseTime {

    @CreatedDate
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime modifiedDate;
}

해당 어노테이션은 객체의 입장에서 '생성 시간', '수정 시간' 같은 공통 매핑 정보가 필요할 때
사용됩니다.

@MappedSuperclass 어노테이션을 사용함으로써 JPA Entity 클래스들이 BaseTime class를 상속할 경우 BaseTime class의 필드인 createdDate, modifiedDate를 인식하도록 합니다.

@MappedSupperclass가 선언된 클래스는 Entity가 아니며 직접 생성해서 사용될 일이 없기 때문에 대부분 추상 클래스로 만들어집니다.

(위 BaseTime class도 public abstract class BaseTime으로 만드는 것이 원리적으로는 더 맞습니다.)

=> 결론적으로 부모 클래스를 상속받는 자식 클래스에게 매핑 정보만을 제공하고 싶을 때 사용하는 어노테이션입니다.

@CreatedDate / @LastModifiedDate

데이터를 저장할 때 '생성된 시간 정보'와 '수정된 시간 정보'는 여러모로 많이 사용되고 또 중요합니다.

JPA를 사용하면서 @CreatedDate, @LastModifiedDate를 사용하여 생성된 시간 정보, 수정된 시간 정보를 자동으로 저장할 수 있는데요.

오늘 작업 중에 createAt, updatedAt 값이 계속 null로 들어가던 문제를 해결하면서 동작 원리에 대해 조금 더 살펴본 내용을 정리합니다.

(null이 들어간 원인은 main method가 실행되는 Application class에 @EnableJpaAuditing 어노테이션을 적용하지 않아서였습니다.)


@JsonIgnore

JSON 데이터에 해당 프로퍼티는 null로 들어가게 된다. 즉 데이터에 아예 포함이 인되게 한다.


@GetMapping

@GetMapping(path = "/names/{groupName}", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<ApiMessage<List<GroupNameResponse>>> searchGroupByGroupName(@PathVariable String groupName) {
        return (ResponseEntity<ApiMessage<List<GroupNameResponse>>>)
                ApiMessage.getAdminResponseEntity(groupService.searchGroupsByGroupName(groupName));
    }
  • produces (보통 이렇게 쓰인다, produces = MediaType.APPLICATION_JSON_UTF8_VALUE )

    • HTTP 응답 헤더로 "Content-Type: application/json;charset=UTF-8"을 반환한다.
    • 생략할 경우 메써드 리턴 타입에 따라 Content-Type을 자동으로 판단하여 반환한다.
  • consumes (보통 이렇게 쓰인다, consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)

    • HTTP 요청 헤더가 "Content-Type: application/json;charset=UTF-8"인 것만 처리한다.
      다른 값이 들어올 경우 org.springframework.web.HttpMediaTypeNotSupportedException을 발생시킨다.
    • HTTP 요청 헤더에 명시된 Content-Type은 HTTP 요청 바디의 형식을 의미한다. 즉, 서버에서는 JSON 형식의 바디만 처리하겠다는 의미이다.
    • 참고로 GET 요청은 바디를 가지지 않으므로 파라미터를 명시할 필요가 없다.

@JsonInclude

@JsonInclude(JsonInclude.Include.NON_NULL)
private String parentGroupName;

null인 테이터를 제외하고 JSON 생성


@RunWith(SpringRunner.class) → @ExtendWith(SpringExtension.class)

  1. JUnit 프레임워크의 테스트 실행방법을 확장할 때 사용하는 어노테이션
  2. ApplicationContext를 만들고 관리하는 작업을 @RunWith(SpringRunner.class)에 설정된 class로 이용하겠다는 의미

Junit5에서 RunWith를 사용할 수 없는 이유

  • JUnit5로 넘어오면서 @RunWith는 @ExtendWith로 변환하게 되었습니다.
  • @RunWith(SpringRunner.class) → @ExtendWith(SpringExtension.class)
  • 만약 @SpringBootTest를 설정하셨다면, cmd(control) + @SpringBootTest클릭으로 다음과 같이 이미 SpringBootTest에 적용이 되어 있기 때문에 @SpringBootTest를 이용한다면 생략할 수 있음을 알 수 있습니다.

@PostConstruct

딱 한번만 받아오면 되는 값을 사용 시 마다 요청을 새로 호출하는 실수를 했다. @PostConstruct 를 사용하면 WAS 가 뜰 때 한번만 기본값을 세팅해두고 두고두고 쓸 수 있다.

@PostConstruct 는 WAS 가 뜰 때 bean이 생성된 다음 딱 한번만 실행된다.

@Component
public class DbInit {

    @Autowired
    private UserRepository userRepository;

    @PostConstruct
    private void init() {
        // 초기화 처리
        User admin = new User("admin", "admin password");
        User normalUser = new User("user", "user password");
        userRepository.save(admin, normalUser);
    }
}

Spring은 bean을 초기화 한 이후에 @PostConstruct을 한번만 호출한다. 즉 @PostConstruct는 WAS 가 뜰 때 bean이 생성된 다음 딱 한번만 실행된다. 따라서 @PostConstruct 를 사용하여 기본 사용자라던가, 딱 한번만 등록하면 되는 key 값 등을 등록하여 사용할 수 있다.


@RunWith(SpringRunner.class)

  1. @SpringBootTest를 사용하면 application context를 전부 로딩해서 자칫 잘못하면 무거운 프로젝트로서의 역할을 할 수 있다.

  2. but, Junit4에서 지원하는 @RunWith(SpringRunner.class)를 사용하면 @Autowired, @MockBean에 해당되는 것들만 application context를 로딩하게 되므로 Junit4에서 필요한 조건에 맞춰서 @RunWith(SpringRunner.class)를 사용하게 된다.


@WebMvcTest / @AutoConfigureMockMvc

테스트를 위해 실제 객체와 비슷한 객체를 만드는 것을 모킹(Mocking)이라고 하고, 테스트 하려는 객체가 복잡한 의존성을 가지고 있을 때, 모킹한 객체를 이용하면 의존성을 단절시킬 수 있어 쉽게 테스트 가능하다.

  • 공통점 : 웹 애플리케이션에서 컨트롤러를 테스트 할 때, 서블릿 컨테이너를 모킹하기 위해서는 @WebMvcTest 또는 @AutoConfigureMockMvc를 사용하면 된다.
@WebMvcTest @AutoConfigureMockMvc
공통점 웹 애플리케이션에서 컨트롤러를 테스트 할 때, 서블릿 컨테이너를 모킹하기 위해서는  @WebMvcTest  또는 @AutoConfigureMockMvc를 사용하면 된다.
차이점 - 웹에서 테스트하기 힘든 컨트롤러를 테스트 하는데 적합

- 웹 상에서 요청과 응답에 대해 테스트할 수 있을 뿐만 아니라 시큐리티 혹은 필터까지 자동으로 테스트하여 수동으로 추가/삭제 가능- 일반적으로 @MockBean 또는 @Import와 함께 사용되어 @Controller 빈에 필요한 협력자를 생성한다.

- 여러 스프링 테스트 어노테이션 중 Web에 집중할 수 있는 어노테이션- 선언할 경우  @Controller,@ControllerAdvice등을 사용 가능

- 단, @Service, @Component, @Repository 등은 사용 불가
- @WebMvcTest와 비슷하게 사용할 수 있는 어노테이션이다.

- @WebMvcTest와 가장 큰 차이점은 컨트롤러 뿐만 아니라 테스트 대상이 아닌 @Service나 @Repository사 붙은 객체들도 모두 메모리에 올린다.

- 간단하게 테스트 하기 위해서는 @AutoConfigureMockMvc가 아닌 @WebMvcTest를 사용해야 한다.

- MockMVC를 보다 세밀하게 제어하기 위해 사용하며,전체 애플리케이션 구성을 로드하고 MockMVC를 사용하려는 경우 @WebMvcTest보다 @AutoConfigureMockMvc와 결합된 @SpringBootTest를 고려해야 한다.
profile
The Office Lover

0개의 댓글