🔍 Spring boot 어노테이션 정리

기초 CRUD를 위한 backend 구현 중 사용한 spring boot 어노테이션을 소개하는 시간을 가져보겠습니다. 예시로 사용한 코드는 개인적으로 toy-project로 사용했던 todo webpage 제작한 코드를 활용했습니다. 이는 github에 올라와 있으니 참고하실 분은 url을 참고하시길 바랍니다. 개인 github/toy-todo-back

✒️ @SpringBootApplication

package com.everything.todo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

SpringBootApplication 어노테이션은 해당 클래스가 스프링부트를 설정하는 클래스임을 의미합니다. 스프링에서는 이 어노테이션이 달린 클래스가 있는 패키지를 베이스 패키지로 간주합니다.

우리가 스프링을 사용하는 가장 큰 이유 중 하나는 의존성 주입 컨테이너로써의 역할 때문입니다. 스프링은 베이스 패키지와 그 하위 패키지에서 자바 빈(Bean)을 찾아 스프링의 의존성 주입 컨테이너 오브젝트(ApplicationContext)에 등록하게 됩니다. 그리고 애플리케이션 실행 중 어떤 오브젝트가 필요한 경우, 의존하는 다른 오브젝트를 찾아 연결해주죠.

✒️ @Component 와 @Bean

스프링이 개발자 대신 객체를 제어하기 위해서는 객체들이 빈(Bean)으로 등록되어 있어야 하죠. 이를 스프링 어노테이션으로 간단하게 작업할 수 있습니다.

스프링 MVC에서는 @Controller 등으로 빈으로 등록할 수 있으면 config와 관련된 객체들은 두가지 방법으로 객체를 빈으로 등록할 수 있습니다. config 관련 객체를 등록하는 첫번째 방법이 @Component입니다.

@Component
public class Utility {
   // ...
}

@Component는 클래스 레벨에서 선언함으로써 스프링이 런타임시에 컴포넌트 스캔을 하여 자동으로 빈을 찾고 등록하도록 하는 어노테이션입니다. 편리해 보이지만 모든 경우에 자동화가 정답인 것은 아닙니다.

엔터프라이즈 애플리케이션(Enterprise Application;EA, 비지니스 또는 정부와 같은 회사 환경에서 작동하도록 설계된 대규모 SW 시스템 플랫폼)의 경우 엔지니어가 오브젝트를 생성하고 사용하는 경로를 정확히 알아야 하는 경우가 많습니다.

또한 다른 라이브러리를 사용하는데, 이 라이브러리 클래스가 스프링 기반이 아니라서 @Component를 추가하지 못하는 경우도 있죠. 이런 경우에는 스프링으로 빈을 관리하기 위해 직접 스프링에게 작업 지시를 해야할 경우가 생깁니다. 따라서 두번째 방법은 @Bean입니다.

@Configuration
public class AppConfig {
   @Bean
   public MemberService memberService() {
      return new MemberServiceImpl();
   }
}

@Bean 어노테이션은 위의 예시와 같이 메소드 레벨에서 선언하며, 반환되는 객체(인스턴스)를 엔지니어가 직접 '이 빈은 이렇게 생성해라'하고 말해줄 필요가 있습니다. 다시 말해 @Bean을 이용해 우리는 스프링에게 이 오브젝트를 정확히 어떻게 생성해야 하는지, parameter를 어떻게 넣어줘야 하는지 알려줘야 합니다.

✒️@Buider & @NoArgsConstructor & @AllArgsConstructor & @Data

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class TodoDTO {
  ...
  
  public TodoDTO(final TodoEntity entity) {
    ...
  }
}

@Builder는 오브젝트 생성을 위한 디자인 패턴 중 하나입니다. 롬복(lombok, Java 라이브러리로 반복되는 메서드 작성 코드를 줄여주는 코드 다이어트 라이브러리)이 제공하는 @Builder 어노테이션을 사용하면 따로 builder 클래스를 개발하지 않아도 Builder 패턴을 사용하여 오브젝트를 생성할 수 있습니다.

@NoArgsConstructor 어노테이션은 매개변수가 없는 생성자를 구현해줍니다.
반대로 @AllArgsConstructor 어노테이션은 클래스의 모든 멤버변수를 매개변수로 받는 생성자를 구현해줍니다.

@Data 어노테이션은 클래스 멤버 변수의 Getter/Setter 메서드를 구현해줍니다.

✒️@RestController & @RequestMapping & @GetMapping

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("test") //리소스
public class TestController {

  @GetMapping
  public String testController() {
    return "Hello World!";
  }
  
  @GetMapping("/testGetMapping")
  public String testControllerWithPath() {
    return "Hello World! testGetMapping ";
  }
	...
}

REST API를 구현하기 위해 @RestController 어노테이션을 이용하여 해당 컨트롤러가 RestControlleer임을 명시해주어야 합니다. 이 어노테이션은 http 관련 코드 및 요청/응답 매핑을 스프링이 알아서 해주도록 합니다.

또한 우리는 특정 uri로 요청을 보내면 Controller에서 어떤 방식으로 처리할지를 정의합니다. 이때 들어온 요청을 특정 메서드와 매핑하기 위해서 사용되는 것이 @RequestMapping입니다. 공통적인 url은 class에 @RequestMapping으로 설정한다.

@GetMapping 어노테이션을 이용해 메서드의 리소스와 HTTP 메서드를 지정할 수 있습니다. 위의 코드의 경우 클라이언트가 "test" 리소스에 대해 Get 메서드로 요청하면, @GetMapping에 연결된 컨트롤러가 실행됩니다.

@GetMapping("/testGetMapping") 어노테이션을 통해서 /test/testGetMapping이 이 메서드에 연결되었다는 사실을 알 수도 있습니다.

@GetMapping과 비슷한 어노테이션으로 @PostMapping, @PutMapping, @DeleteMapping이 있는데, 각각 HTTP 메서드 POST, PUT, DELETE를 의미합니다.

✒️@PathVariable & @RequestParam & @RequestBody

@RestController
@RequestMapping("test") //리소스
public class TestController {
	
  @GetMapping("/{id}")
  public String testControllerWithPathVariables(@PathVariable(required = false) int id) {
    return "Hello World! ID " + id;
  }

  // /test경로는 이미 존재하므로 /test/testRequestParam으로 지정했다.
  @GetMapping("/testRequestParam")
  public String testControllerRequestParam(@RequestParam(required = false) int id) {
    return "Hello World! ID " + id;
  }

  // /test경로는 이미 존재하므로 /test/testRequestBody로 지정했다.
  @GetMapping("/testRequestBody")
  public ResponseDTO<String>  testControllerRequestBody(@RequestBody TestRequestBodyDTO testRequestBodyDTO) {
    List<String> list = new ArrayList<>();
    list.add("Hello World! I'm ResponseDTO");
    ResponseDTO<String> response = ResponseDTO.<String>builder().data(list).build();
    return response;
  }

@PathVariable을 이용하면 /{id}와 같이 URI의 경로로 넘어오는 값을 변수로 받아 올 수 있습니다. 매개변수 /{id}는 경로로 들어오는 임의의 숫자 또는 문자를 변수 id에 매핑하라는 뜻입니다. (required = flase)는 이 매개변수가 꼭 필요한 것은 아니라는 뜻입니다. 따라서 test/뒤에 id가 명시되지 않더라도 에러는 나지 않을 것입니다.

@RequestParam과 @RequestBody는 위의 @PathVariable과 동일한 동작을 수행합니다. 다만 @RequestBody는 보통 반환하고자 하는 리소스가 복잡할 때 사용합니다. 예를들어서 String이나 int같은 기본 자료형이 아닌 오브젝트처럼 복잡한 자료형을 통째로 요청에 보내고 싶은 경우가 이에 해당합니다.

✒️@RestController

@RestController
@RequestMapping("todo")
public class TodoController {

}

@RestController는 안을 들여다보면 크게 두 어노테이션으로 이루어져 있습니다. 하나는 Controller이고 하나는 ResponseBody입니다.

@Controller
@RespnseBody
public @interface RestController{
...
}

@Controller는 다시 @Component로 스프링이 이 클래스의 오브젝트를 자동으로 생성하고 다른 오브젝트들과의 의존성을 연결합니다. @ResponseBody는 이 클래스의 메서드가 리턴하는 것은 웹 서비스의 ResponseBody라는 뜻입니다. 다시 말해, 메서드가 리턴할 때 스프링은 리턴된 오브젝트를 JSON 형태로 바꾸고 HttpResponse에 담아 반환하게 됩니다.

위 방법으로 문자열보다 복잡한 오브젝트 같은 형태를 리턴할 수 있다.

✒️@Service

@Service
public class TodoService {

  public String testService() {
    ...
  }
}

@Service 어노테이션은 스테레오타입 어노테이션입니다. 어노테이션 내부에 @Component 어노테이션을 가지고 있습니다. 스프링 컴포넌트이며 기능적으로는 비지니스 로직을 수행하는 서비스 레이어임을 알려주는 어노테이션입니다.

✒️JPA 관련 어노테이션

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity
@Table(name = "Todo")
public class TodoEntity {
  @Id
  @GeneratedValue(generator="system-uuid")
  @GenericGenerator(name="system-uuid", strategy = "uuid")
  private String id; // 이 오브젝트의 아이디
  private String userId; // 이 오브젝트를 생성한 유저의 아이디
  private String title; // Todo 타이틀 예) 운동 하기
  private boolean done; // true - todo를 완료한 경우(checked)
}

JPA관련 어노테이션을 이용해 자바 클래스를 엔티티로 정의할 때 몇가지 주의해야 할 점이 있습니다.

  1. 클래스에는 매개변수가 없는 생성자, NoArgsContructor가 필요합니다.
  2. Getter/Setter가 필요합니다.
  3. 기본키를 지정해줘야 합니다.

📌@Entity

자바 클래스를 엔티티로 지정하기 위해서는 위의 코드처럼 TodoEntity에 @Entity를 추가해야 합니다. 엔티티에 이름을 부여하고 싶다면 @Entity("TodoEntity")처럼 매개변수를 넣어 줄 수 있습니다.

📌@Table(name="...")

테이블 이름을 지정하기 위해서 해당 어노테이션을 삽입해야 합니다. 이 엔티티는 데이터베이스의 parameter로 명시한 테이블에 자동으로 맵핑될 것입니다. 위의 코드의 경우 데이터베이스의 Todo 테이블에 매핑됩니다.
만약 @Table을 추가하지 않거나, 추가해도 name을 명시하지 않는다면 @Entity의 이름을 테이블 이름으로 간주하게 됩니다.
@Entity에도 이름이 지정되어 있지 않다면 클래스의 이름을 테이블 이름으로 간주하게 됩니다.

📌@Id

@Id는 기본키가 될 필드에 지정합니다. 위 코드의 경우 id가 기본 키 이므로 id 필드 위에 @Id를 추가해야 합니다. Id 필드는 오브젝트를 데이터베이스에 저장할 때마다 생성할 수도 있지만, @GeneratedValue 어노테이션을 이용해 자동으로 생성할 수도 있습니다.

📌@GeneratedValue & @GenericGenerator

앞서 말했듯이 해당 어노테이션이 달린 필드를 자동으로 생성하겠다는 뜻입니다. 이 때, parameter인 generater로 어떻게 ID를 생성할지 지정할 수 있습니다. 위 코드의 경우 system-uuid라는 generateor를 사용한다고 명시했죠.

이 때, system-uuid는 @GenericGenerator에 정의된 generator의 이름입니다. @GenericGenerator는 Hibernate가 제공하는 기본 Generator가 아닌 커스텀 generator를 사용하고 싶을 때 이용하죠. 기본 Generator로는 INCREMENTAL, SEQUENCE, IDENTITY 등이 있지만, 우리는 문자열 형태의 uuid를 사용하기 위해 커스텀 Generator를 만들었습니다. uuid를 사용하기 위해 GenericGenerator의 매개변수 strategy로 "uuid"를 넘겼습니다.

✒️@Repository

@Repository
public interface TodoRepository extends JpaRepository<TodoEntity, String> {
  List<TodoEntity> findByUserId(String userId);
   ...
}

우선 알아두어야 할 것은, JpaRepository라는 인터페이스입니다. 이 인터페이스를 사용하기 위해서는 새 인터페이스를 작성해 JpaRepository를 확장(extend)해야 합니다. 이때 JpaReposirotey<T, ID>가 Genetic Type을 받는 것을 주의해야 됩니다. 첫 번째, 매개변수인 T는 테이블에 매핑할 엔티티 클래스이고, ID는 이 엔티티의 기본 키 타입입니다. 위 코드의 경우 TodoEntity와 그 기본 키인 id의 타입인 String을 넣어 주었습니다.

여기서 사용한 @Repository는 위에서 살펴봤던 다른 어노테이션과 유사하게 Component 어노테이션의 특별한 케이스로써 사용됩니다. 따라서 Repository 어노테이션이 달린 인터페이스는 스프링이 자동적으로 관리하게 됩니다.

✒️@Query

@Repository
public interface TodoRepository extends JpaRepository<TodoEntity, String> {
  List<TodoEntity> findByUserId(String userId);

  @Query("SELECT t FROM TodoEntity t WHERE t.userId = ?1")
  TodoEntity findByUserIdQuery(String userId);
}

복잡한 형태의 쿼리는 @Query 어노테이션을 이용해 지정할 수 있습니다.
구체적인 메서드 이름을 작성하는 방식은 공식 사이트의 레퍼런스를 통해 확인하는 것이 가장 적확하므로 url을 남겨놓겠습니다. 쿼리 메서드 작성 방식

✒️로그 어노테이션

서비스 구현 중에 디버깅을 유용하게 하기 위해서 주로 로그 설정이라는 방식을 많이 사용하죠. 가장 간단한 방법으로는 코드 사이사이에 출력문을 넣어주는 것입니다.

System.out.prinln();

유용하지만 물론 기능이 제한적입니다. 어떤 로그는 그냥 정보를 위한 것이고, 아니면 디버깅을 위한 자세한 정보를 보일 수도 있는데 위의 방법으로는 다양한 경우에 대해 차별성을 가질 수 없죠.

이렇게 용도에 따라 로그를 크게 info, debug, warn,error로 나누고 이를 로그 레벨이라고 부릅니다. 다양한 로그 레벨을 단순하게 출력문으로 구현할 수 있겠지만 이런 기능을 한번에 제공하는 라이브러리가 존재합니다. 바로 Slf4j 라이브러리입니다.

📌@Slf4j

시중에 많은 로그 라이브러리가 나와 있지만 Slf4j는 로그계의 JPA 쯤 됩니다. Slf4j를 구현부에 연결해서 스프링에게 로깅을 맡기겠습니다.

@Slf4j
@Service
public class TodoService {

  @Autowired
  private TodoRepository repository;

}

0개의 댓글