Spring Boot 핵심 개념 완벽 정리 - Bean부터 REST API까지

geoson·2025년 6월 26일

Spring & 백엔드

목록 보기
18/18

Spring을 처음 배울 때 헷갈리는 개념들을 차근차근 정리해보았습니다. 실무에서 가장 많이 사용하는 핵심 기능들을 중심으로 설명하겠습니다!

1. Bean - Spring의 핵심

Bean이란? Spring이 실행될 때 @Component 계열 어노테이션을 확인해서 객체로 등록하는 것

@Service
public class UserService {
    public void saveUser(User user) {
        // 비즈니스 로직
    }
}

@Repository
public class UserRepository {
    public void save(User user) {
        // 데이터 저장 로직
    }
}

핵심 어노테이션들:

  • @Component: 일반적인 Bean
  • @Service: 비즈니스 로직 담당
  • @Repository: 데이터 접근 담당
  • @Controller: 웹 요청 처리 담당

2. DI (Dependency Injection) - 의존성 주입

DI란? Spring 객체를 가져다 편하게 사용할 수 있게 의존성 주입을 하는 것

생성자 주입 (권장)

@Service
public class UserService {
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

왜 생성자 주입이 좋을까?

  • 불변성 보장 (final 키워드 사용 가능)
  • 테스트하기 쉬움
  • 의존성이 명확히 보임
  • NPE 방지

필드 주입 (비권장)

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository; // 간단해 보이지만 문제점이 많음
}

3. @Qualifier - 같은 타입 Bean이 여러 개일 때

@Qualifier란? 의존성이 두 가지 이상일 때 어떤 것을 사용할지 정해주는 것

@Repository
@Qualifier("mysql")
public class MySqlUserRepository implements UserRepository { }

@Repository
@Qualifier("mongo")
public class MongoUserRepository implements UserRepository { }

@Service
public class UserService {
    public UserService(@Qualifier("mysql") UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

4. @Configuration + @Bean - 외부 라이브러리 등록

언제 사용? 외부 라이브러리를 Spring Bean으로 등록할 때

@Configuration
public class DatabaseConfig {
    
    @Bean
    public DataSource dataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        dataSource.setUsername("user");
        dataSource.setPassword("password");
        return dataSource;
    }
}

과정:
1. dependency 추가 (pom.xml) → 라이브러리 파일을 프로젝트에 포함
2. @Bean 등록 → 라이브러리의 객체를 Spring Bean으로 등록

5. AOP와 @Transactional - 롤백을 위한 어노테이션

@Transactional이란? 데이터베이스 작업의 안전성을 보장하는 어노테이션

문제 상황

public void transfer(String from, String to, int amount) {
    accountRepository.withdraw(from, amount);  // 성공
    accountRepository.deposit(to, amount);     // 실패!
    // 결과: 돈만 사라짐 😱
}

해결책

@Transactional
public void transfer(String from, String to, int amount) {
    accountRepository.withdraw(from, amount);  // 성공
    accountRepository.deposit(to, amount);     // 실패!
    // Spring이 자동으로 롤백 → 원상복구 😊
}

동작 원리 (프록시 패턴):

// Spring이 자동으로 이렇게 처리
public void transfer() {
    TransactionManager.begin();        // 트랜잭션 시작
    try {
        실제_transfer_메서드();           // 진짜 메서드 실행
        TransactionManager.commit();   // 성공시 커밋
    } catch (Exception e) {
        TransactionManager.rollback(); // 실패시 롤백
        throw e;
    }
}

6. Spring Boot 설정 관리

application.properties 설정

# 서버 설정
server.port=8080

# 데이터베이스 설정
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=1234

# JPA 설정
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

@Value로 설정값 사용

@Service
public class EmailService {
    @Value("${app.email.from}")
    private String fromEmail;
    
    @Value("${app.email.enabled:true}")  // 기본값 설정
    private boolean emailEnabled;
}

Profile로 환경 분리

application-dev.properties (개발용)

spring.datasource.url=jdbc:h2:mem:testdb
logging.level.com.example=DEBUG

application-prod.properties (운영용)

spring.datasource.url=jdbc:mysql://prod-server:3306/prod_db
logging.level.com.example=WARN

활성화:

spring.profiles.active=dev

7. Spring Data JPA - 데이터베이스 연동

Entity 클래스

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "username")
    private String name;
    
    private String email;
    private Integer age;
    
    // 생성자, getter, setter
}

Repository 인터페이스

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // 기본 CRUD 자동 제공: save(), findById(), findAll(), delete()
    
    // 메서드 이름으로 자동 쿼리 생성!
    List<User> findByName(String name);
    List<User> findByAgeGreaterThan(Integer age);
    List<User> findByNameContainingAndAgeGreaterThan(String name, Integer age);
    
    // 커스텀 쿼리
    @Query("SELECT u FROM User u WHERE u.email LIKE %:domain%")
    List<User> findByEmailDomain(@Param("domain") String domain);
}

Optional 활용

@Service
public class UserService {
    public User findUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다"));
    }
    
    public User findUserByIdOrNull(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

8. REST API 만들기

Controller 작성

@RestController
@RequestMapping("/api/users")
public class UserController {
    private final UserService userService;
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }
    
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.findUserById(id);
    }
    
    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.saveUser(user);
    }
    
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id);
        return userService.saveUser(user);
    }
    
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}

쿼리 파라미터 처리

@GetMapping("/search")
public List<User> searchUsers(@RequestParam String name, 
                             @RequestParam(required = false) Integer age) {
    if (age != null) {
        return userService.findUsersByNameAndAge(name, age);
    } else {
        return userService.findUsersByName(name);
    }
}

9. 전체 아키텍처 흐름

HTTP 요청 (JSON)
    ↓
@RestController (Bean)
    ↓ DI로 주입받은
@Service (Bean) + @Transactional
    ↓ DI로 주입받은
@Repository (Bean) + JPA
    ↓
Database

실제 예시

// 1. HTTP 요청: POST /api/users
@RestController
public class UserController {
    private final UserService userService; // DI
    
    @PostMapping("/api/users")
    public User createUser(@RequestBody User user) {
        return userService.saveUser(user);
    }
}

// 2. 비즈니스 로직 처리
@Service
public class UserService {
    private final UserRepository userRepository; // DI
    
    @Transactional // AOP로 트랜잭션 관리
    public User saveUser(User user) {
        if (userRepository.findByEmail(user.getEmail()).isPresent()) {
            throw new IllegalArgumentException("이미 존재하는 이메일");
        }
        return userRepository.save(user);
    }
}

// 3. 데이터베이스 접근
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email); // JPA가 SQL 자동 생성
}

정리

핵심 개념들:

  • Bean: Spring이 관리하는 객체들
  • DI: Bean들을 서로 연결해주는 의존성 주입
  • @Transactional: 데이터 안전성을 위한 롤백 기능
  • JPA: 데이터베이스와 객체 자동 연결
  • Profile: 환경별 설정 분리
  • REST API: 웹에서 JSON으로 데이터 주고받기

이 개념들만 제대로 이해해도 Spring Boot로 실무 프로젝트를 시작할 수 있습니다! 🚀

0개의 댓글