[Spring] 어노테이션 이해하기

KUKJIN LEE's profile picture

KUKJIN LEE1개월 전 작성

다양한 어노테이션을 통해 코드를 효율적으로 작성하고, 개발자들이 더 직관적으로 애플리케이션을 설계할 수 있도록 돕습니다.

 

Spring에서 어노테이션은 특정 역할과 책임을 명시적으로 표현하며, 코드의 가독성과 유지보수성을 높입니다. 어노테이션을 통해 빈 관리, 트랜잭션 처리, 웹 요청 처리 등의 복잡한 작업을 간현하게 처리할 수 있습니다.

 

자주 사용되는 주요 어노테이션

  • @Component: 기본적인 스프링 빈을 정의하는 어노테이션입니다. 스프링이 해당 클래스를 빈으로 관리하게 됩니다.

  • @Service: 비즈니스 로직을 처리하는 클래스에 사용됩니다. 서비스 계층에서 주로 활용됩니다.

  • @Controller: 웹 요청을 처리하는 컨트롤러 클래스에 사용됩니다. 클라이언트의 요청을 받아서 적절한 응답을 반환합니다.

  • @Repository: 데이터 접근 계층(DAO)에서 사용되며, 주로 데이터베이스와의 상호작용을 담당하는 클래스에 사용됩니다.

  • @RestController: RESTful API를 처리하는 컨트롤러에 사용됩니다. 자동으로 응답 데이터를 JSON 또는 XML 형식으로 변환합니다.

  • @Autowired: 의존성 주입을 자동으로 처리해주는 어노테이션입니다. 주로 생성자, 필드, 메서드에 사용되어 필요한 빈을 주입합니다.

  • @Transactional: 트랜잭션 관리를 위한 어노테이션으로, 트랜잭션 범위 내에서 데이터베이스 작업이 처리되도록 보장합니다.

  • @Configuration: 설정 파일을 정의하는 데 사용됩니다. 스프링 컨테이너에 하나 이상의 빈을 설정하고 등록하는 클래스를 나타냅니다.

  • @Bean: 빈을 정의할 때 사용되며, 주로 @Configuration 클래스 내에서 특정 객체를 빈으로 등록할 때 활용됩니다.

 

어노테이션을 효과적으로 사용하는 법

1) @Service vs @Component: 언제 사용해야 할까?

  • @Component는 스프링 빈을 정의하는 가장 기본적인 어노테이션입니다. 그럼에도 불구하고 @Service를 사용하는 이유는 의도를 명확하게 하기 위함입니다. @Service를 붙이면 해당 클래스가 비즈니스 로직을 처리하는 서비스 계층임을 명확히 알 수 있습니다.

2) @Controller vs @RestController: 차이점은 무엇인가?

  • @Controller는 웹 요청을 처리하지만, 별도로 @ResponseBody를 사용해야 응답을 반환할 수 있습니다.

  • @RestController@Controller@ResponseBody를 결합한 형태로, 모든 메서드가 자동으로 JSON 또는 XML 데이터를 반환하므로, REST API를 작성할 때 유용합니다.

3) @Autowired: 생성자 주입을 활용한 안전한 의존성 주입

  • @Autowired는 주로 생성자 주입을 통해 사용하는 것이 권장됩니다. 생성자 주입은 불변성을 보장하고, 테스트 작성 시 모킹(Mocking)이 더 쉬워집니다.

@Service
public class UserService {

    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // 비즈니스 로직
}

 

4) @Transactional: 트랜잭션 관리로 데이터 안전성 확보

  • 데이터베이스 작업을 수행할 때 @Transactional을 사용하여 트랜잭션 처리를 보장할 수 있습니다. 트랜잭션 안에서 모든 작업이 성공해야만 커밋되며, 하나라도 실패하면 롤백됩니다.

@Service
public class UserService {

    @Transactional
    public User createUser(String name, String email) {
        User user = new User(name, email);
        return userRepository.save(user);
    }
}

 

3. 실습 예시: 사용자 생성 기능

1) 사용자 엔티티 클래스 (User.java)

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
}
  • @Data: Lombok 어노테이션으로 getter, setter, toString, equals, hashCode를 자동 생성합니다.

  • @AllArgsConstructor: 모든 필드를 포함하는 생성자를 자동 생성합니다.

  • @NoArgsConstructor: 기본 생성자를 자동 생성합니다.

  • @Entity: 이 클래스가 JPA 엔티티임을 나타내며, 데이터베이스 테이블에 매핑됩니다.

 

2) 사용자 리포지토리 인터페이스 (UserRepository.java)

기본적인 CRUD 기능(예: save(), findById(), findAll())을 제공하는 것뿐만 아니라, 데이터 접근을 구조화하고 유지보수성을 높이기 위해 반드시 필요합니다.

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByEmail(String email);  // 이메일로 사용자 조회
}

 

3) 사용자 서비스 클래스 (UserService.java)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public User createUser(String name, String email) {
        User user = new User(null, name, email);  // 사용자 객체 생성 (ID는 null로 설정)
        return userRepository.save(user);  // UserRepository를 통해 저장
    }

    public User getUserByEmail(String email) {
        return userRepository.findByEmail(email);  // 이메일을 통해 사용자 조회
    }
}
  • createUser(): 사용자를 생성하고 데이터베이스에 저장합니다.

  • getUserByEmail(): 이메일로 사용자를 조회합니다.

 

4) 사용자 컨트롤러 클래스 (UserController.java)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

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

    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/create")
    public ResponseEntity<User> createUser(@RequestParam String name, @RequestParam String email) {
        User newUser = userService.createUser(name, email);
        return new ResponseEntity<>(newUser, HttpStatus.CREATED);
    }

    @GetMapping("/find")
    public ResponseEntity<User> findUserByEmail(@RequestParam String email) {
        User user = userService.getUserByEmail(email);
        if (user != null) {
            return new ResponseEntity<>(user, HttpStatus.OK);
        } else {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }
}
  • createUser(): 새로운 사용자를 생성하여 데이터베이스에 저장합니다.

  • findUserByEmail(): 이메일로 사용자를 조회하고, 해당 사용자가 없을 경우 404 Not Found 응답을 반환합니다.

New Tech Posts