Published on

[ Spring ] 스프링 어노테이션, 객체 정리

Authors
  • avatar
    Name
    유사공대생
    Twitter

정리를 하게 된 계기

요즘 스프링을 공부할 때 잘 짜시는 분의 코드를 깃허브에서 긁어와서 분석하면서 적용하고 있다. 그래서 그런지 나도 모르는 어노테이션을 적용하는 경우가 많았다. 그래서 그분의 코드를 제 프로젝트에 적용하면서 어노테이션을 적용하는 이유를 정리해가면서 사용하려고 한다.

스프링의 어노테이션(annotation)이란?

어노테이션이란 자바 코드에 추가적인 정보를 제공하는 메타데이터(metadata)이다. 어노테이션을 사용하여 클래스, 메소드, 변수 등에 대한 정보를 표현하고, 이 정보를 프로그램에서 활용할 수 있다.

어노테이션은 @ 기호로 시작하며, 컴파일러나 프레임워크 등에서 이 정보를 읽어 활용할 수 있다. 예를 들어, 스프링(Spring) 프레임워크에서는 @Controller, @Service, @Repository 등의 어노테이션을 사용하여 스프링이 해당 클래스를 어떻게 활용해야 하는지를 나타내며, 이 정보를 바탕으로 스프링 컨테이너가 빈(bean)을 생성하고 관리할 수 있다.

어노테이션은 사용자가 직접 정의할 수도 있으며, 이러한 사용자 정의 어노테이션을 사용하면 해당 어노테이션이 적용된 요소에 대한 추가적인 정보를 제공할 수 있다. 이를 통해 개발자는 코드를 더욱 명확하게 작성할 수 있고, 다른 개발자들이 코드를 이해하기 쉬워진다.

ResponseEntity 객체

스프링(Spring)에서는 ResponseEntity를 사용하여 HTTP 응답(Response)을 생성할 수 있다. ResponseEntity는 HTTP 응답 본문(body), 상태 코드(status code), 응답 헤더(response headers) 등을 포함할 수 있으며, 클라이언트에게 전송될 응답을 정의할 때 사용한다.

ResponseEntity는 제네릭 클래스로, 응답 본문의 타입을 명시할 수 있다. 예를 들어, ResponseEntity<String>은 String 타입의 응답 본문을 포함하는 HTTP 응답을 나타낸다.

ResponseEntity의 주요 메소드는 다음과 같다.

  • getBody() : 응답 본문을 반환한다.
  • getStatusCode() : 응답 상태 코드를 반환한다.
  • getHeaders() : 응답 헤더를 반환한다.

ResponseEntity를 사용하면 HTTP 응답에 대한 세밀한 제어가 가능해지며, RESTful API를 개발할 때 유용하다. 예를 들어, 클라이언트에서 요청한 데이터가 없는 경우 404 Not Found 상태 코드와 함께 응답을 반환하거나, 클라이언트의 요청이 잘못된 경우 400 Bad Request 상태 코드와 함께 응답을 반환할 수 있다.

created()

네, ResponseEntity에는 created라는 정적 메소드가 있습니다. created 메소드는 HTTP 201 Created 상태 코드와 함께 리소스 생성 요청에 대한 응답을 생성할 때 사용됩니다.

created 메소드의 구문

public static <T> ResponseEntity<T> created(URI location)
  • T : 응답 본문의 타입을 나타낸다.
  • location : 생성된 리소스의 위치를 나타내는 URI를 전달한다.

예를 들어, 다음 코드는 created 메소드를 사용하여 HTTP 201 Created 상태 코드와 함께 새로운 리소스를 생성하는 응답을 생성하는 방법을 보여준다.

@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
    // 새로운 유저를 생성하고, 생성된 유저를 location URI에 추가힌다.
    User createdUser = userService.createUser(user);
    URI location = ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(createdUser.getId())
                        .toUri();
    // HTTP 201 Created 상태 코드와 생성된 유저를 반환한다.
    return ResponseEntity.created(location).body(createdUser);
}

위 코드에서는 새로운 유저를 생성하고, 생성된 유저를 나타내는 URI를 location 변수에 저장한다. 그리고 created 메소드를 사용하여 HTTP 201 Created 상태 코드와 함께 해당 URI를 반환한다. 반환된 ResponseEntity의 본문으로는 생성된 유저 객체가 포함된다.

ServletUriComponentsBuilder 클래스

ServletUriComponentsBuilder는 Spring에서 제공하는 URI 빌더 클래스로, Servlet 기반의 애플리케이션에서 사용된다. ServletUriComponentsBuilder를 사용하면 현재 요청(Request)에 대한 URI를 쉽게 생성할 수 있다.

ServletUriComponentsBuilderfromCurrentRequest() 메소드는 현재 요청(Request)의 정보를 기반으로 URI를 생성한다. 예를 들어, 현재 요청의 URL과 파라미터 등의 정보를 이용하여 URI를 생성할 수 있다. 또한 path() 메소드를 사용하여 URI 경로를 추가하고, queryParam() 메소드를 사용하여 URI 쿼리 파라미터를 추가할 수 있다.

예를 들어, 다음 코드는 ServletUriComponentsBuilder를 사용하여 현재 요청(Request)에 대한 URI를 생성하는 방법을 보여준다.

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
    // 사용자 정보를 조회하고, 조회된 사용자 정보를 반환한다.
    User user = userService.getUser(id);
    return ResponseEntity.ok(user);
}

@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
    // 새로운 유저를 생성하고, 생성된 유저를 반환한다.
    User createdUser = userService.createUser(user);
    // 생성된 유저의 정보가 포함된 URI를 생성합니다.
    URI location = ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(createdUser.getId())
                        .toUri();
    // HTTP 201 Created 상태 코드와 생성된 유저를 반환한다.
    return ResponseEntity.created(location).body(createdUser);
}

위 코드에서는 ServletUriComponentsBuilder를 사용하여 새로운 유저를 생성한 후, 생성된 유저를 반환할 때 생성된 유저의 정보가 포함된 URI를 생성한다. 생성된 URI는 created 메소드의 인자로 전달된다.

@Transactional

@Transactional은 Java Spring에서 트랜잭션 처리를 위해 사용되는 애너테이션이다. 트랜잭션은 데이터베이스 상태를 변화시키는 일련의 작업을 하나의 논리적인 단위로 묶는 것을 의미한다. @Transactional을 사용하는 이유는 다음과 같다.

  1. 원자성(Atomicity) 보장: @Transactional을 사용하면 트랜잭션 안에서 실행되는 모든 작업은 원자적으로 처리된다. 즉, 모든 작업이 성공적으로 완료되거나 전혀 수행되지 않는 것을 보장한다. 하나의 작업이 실패하면 이전 상태로 롤백된다.

  2. 일관성(Consistency) 유지: 트랜잭션 내에서 실행되는 모든 작업은 데이터베이스의 일관성을 유지해야한다. 트랜잭션 시작 전과 종료 후의 데이터베이스 상태는 일관된 상태여야 한다. @Transactional은 트랜잭션 경계 내에서 일어나는 모든 변경을 커밋 또는 롤백함으로써 일관성을 보장한다.

  3. 독립성(Isolation) 제공: 트랜잭션은 다른 트랜잭션과 독립적으로 실행됩니다. 각 트랜잭션은 격리 수준을 설정하여 서로의 작업에 영향을 주지 않는다. @Transactional은 트랜잭션의 격리 수준 설정을 지원하여 데이터베이스의 동시성 문제를 방지한다.

  4. 지속성(Durability) 보장: @Transactional을 사용하면 트랜잭션이 성공적으로 완료되면 해당 작업은 영구적으로 데이터베이스에 반영된다. 이는 시스템 오류, 중단 또는 재시작과 같은 예기치 않은 상황에서도 데이터의 지속성을 보장한다.

  5. 예외 처리: @Transactional은 트랜잭션 내에서 예외가 발생할 경우 롤백을 수행하여 데이터의 일관성을 유지한다. 예외가 발생하면 트랜잭션은 롤백되고 이전 상태로 복구된다.

즉, @Transactional은 데이터베이스 작업을 트랜잭션 단위로 묶어서 일관성과 안전성을 보장하고 예외 처리를 간편하게 수행할 수 있도록 도와준다. 이를 통해 데이터베이스 조작에 대한 신뢰성과 안정성을 프로그램에서 유지할 수 있으며, 데이터의 일관성과 무결성을 유지할 수 있다. @Transactional 애너테이션을 사용하면 트랜잭션 관리를 자동화할 수 있고, 복잡한 트랜잭션 관련 로직을 개발자가 직접 구현할 필요가 없어진다. 이는 개발 생산성을 향상시키고 코드의 가독성을 높여준다.

Spring Framework에서 @Transactional 애너테이션은 메서드 레벨 또는 클래스 레벨에서 사용할 수 있다. 메서드에 @Transactional을 추가하면 해당 메서드가 트랜잭션 내에서 실행되는 것으로 설정된다. 또한, @Transactional을 클래스에 추가하면 클래스의 모든 public 메서드에 트랜잭션 설정이 적용된다.

  • 예시
@Transactional
public void transferFunds(Account fromAccount, Account toAccount, BigDecimal amount) {
    // 출금 계좌에서 잔액 차감
    fromAccount.debit(amount);

    // 입금 계좌에 잔액 추가
    toAccount.credit(amount);

    // 트랜잭션이 커밋되면 변경 사항이 데이터베이스에 반영됨
}

위의 코드에서 transferFunds 메서드에 @Transactional 애너테이션이 추가되었다. 이렇게 하면 메서드의 모든 작업은 하나의 트랜잭션으로 묶이게 된다. fromAccount.debit()toAccount.credit() 메서드는 데이터베이스에서 변경이 이루어질 때까지 트랜잭션 내에서 실행된다. 그리고 트랜잭션이 커밋되면 변경 사항이 데이터베이스에 반영된다.

@Transactional은 Spring의 트랜잭션 관리자에 의해 제어되며, 이를 통해 트랜잭션의 시작, 커밋, 롤백 등의 동작을 자동으로 처리한다. 이를 통해 데이터베이스 작업의 일관성과 안정성을 유지하고 예외 처리를 간소화할 수 있다.