1. Spring Bean(빈)
Bean이란 스프링 컨테이너가 객체를 생성(new)하고 관리하며, 필요한 곳에 자동으로 주입해주는 객체이다.
Bean 등록 방법
1) @Component 방식
내가 직접 만든 클래스를 Bean으로 등록할 때 사용한다.
@Service, @Repository, @Controller 모두 내부적으로 @Component를 포함하고 있다.
@Service
public class ProductService {
}
스프링이 실행되면 Component Scan을 통해 @Component 계열의 클래스를 찾아 Bean으로 등록한다.
2) @Configuration + @Bean 방식
수정할 수 없는 외부 라이브러리 클래스 등을 Bean으로 등록할 때 사용한다.
@Configuration
public class AppConfig {
@Bean
public Apple apple() {
return new Apple();
}
}
두 방식의 차이
| 구분 | @Component | @Configuration + @Bean |
| 사용 대상 | 내가 만든 클래스 | 외부 라이브러리 클래스 |
| 객체 생성 | 스프링이 생성 | 개발자가 직접 생성 |
| Bean 이름 | 클래스명(memberService) | 메서드명(apple) |
2. 생성자 주입
Spring에서는 생성자 주입(Constructor Injection) 을 가장 권장한다.
@RequiredArgsConstructor를 사용하면 final 필드에 대한 생성자를 Lombok이 자동으로 생성해준다.
@Service
@RequiredArgsConstructor
public class ProductService {
private final OrderRepository orderRepository;
private final UserRepository userRepository;
}
장점
- 불변 객체 생성 가능
- 의존성 누락 방지
- 테스트가 쉬움
3. Singleton(싱글톤)
Spring Bean은 기본적으로 Singleton으로 관리된다.
즉, 사용자 1000명이 요청하더라도 객체는 하나만 생성되고 모두가 공유해서 사용한다.
잘못된 예
private int price;
필드에 상태를 저장하면 여러 사용자가 동시에 접근하면서 값이 변경될 수 있다.
올바른 예
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
public int order(String userName, int price) {
int orderPrice = price;
...
}
}
지역변수를 사용하면 요청마다 별도의 값이 생성되어 안전하다.
4. JPA 실행 구조
JPA가 실제 SQL을 실행하는 과정은 다음과 같다.
Repository
↓
Spring Data JPA
↓
JPA (표준 명세)
↓
Hibernate
↓
Database
- Spring Data JPA : Repository 구현체를 자동 생성
- JPA : 인터페이스(표준)
- Hibernate : 실제 SQL 생성 및 실행
5. 엔티티 매핑
JPA는 엔티티 클래스를 DB 테이블과 매핑하여 관리한다.
@Entity
@Table(name = "products")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
}
주요 어노테이션
- @Entity : JPA 엔티티
- @Table : 테이블명 지정
- @NoArgsConstructor(PROTECTED) : JPA 기본 생성자
- @Id : 기본키(PK)
- @GeneratedValue : PK 자동 생성(AUTO_INCREMENT)
- @Column : 컬럼 옵션 지정
6. 영속성 컨텍스트(Persistence Context)
영속성 컨텍스트는 엔티티를 관리하는 공간(1차 캐시) 이다.
엔티티 상태
- 비영속(New) : 객체만 생성된 상태, DB와 관계 없음
- 영속(Managed) : 영속성 컨텍스트에서 관리되는 상태
- 준영속(Detached) : 영속성 컨텍스트에서 분리된 상태
- 삭제(Removed) : 삭제 예정 상태
Dirty Checking(변경 감지)
@Transactional 안에서 영속 상태의 엔티티를 수정하면 save()를 호출하지 않아도 변경 사항을 감지하여 트랜잭션 종료 시 자동으로 UPDATE SQL을 실행한다.
@Transactional
public void update() {
Product product = repository.findById(id).get();
product.changeName("사과");
}
→ save() 없이도 UPDATE가 수행된다.
7. 연관관계와 N+1 문제
회원과 주문처럼 엔티티는 서로 연관관계를 가진다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
여기서 LAZY를 사용하면 실제 필요한 시점에 데이터를 조회한다.
N+1 문제
주문 목록 조회
↓
SELECT * FROM orders (1번)
↓
for (Order order : orders) {
order.getMember().getName();
}
↓
회원 조회 쿼리 N번 발생
총 1 + N번의 SQL이 실행된다.
예를 들어 주문이 10개라면
주문 조회 : 1번
회원 조회 : 10번
총 11번
이것이 N+1 문제이다.
해결 방법
필요한 연관 데이터를 처음부터 함께 조회한다.
@Query("""
SELECT o
FROM Order o
JOIN FETCH o.member
""")
List<Order> findAllWithMember();
또는
@EntityGraph(attributePaths = "member")
를 사용하여 한 번의 조회로 필요한 데이터를 가져올 수 있다.
★ 오늘 배운 점
- Spring Bean은 스프링 컨테이너가 생성하고 관리하는 객체이다.
- Bean 등록 방식에는 @Component와 @Configuration + @Bean이 있다.
- Spring Bean은 기본적으로 Singleton으로 관리되므로 상태를 필드에 저장하지 않는 것이 중요하다.
- JPA는 Repository → Spring Data JPA → JPA → Hibernate → DB 순서로 동작한다.
- @Transactional에서는 Dirty Checking을 통해 save() 없이도 변경 사항이 반영된다.
- LAZY 로딩은 성능 최적화에 유리하지만 N+1 문제가 발생할 수 있으며, Fetch Join이나 @EntityGraph를 사용해 해결할 수 있다.
'심화_AI를 활용한 백엔드 아키텍처 심화 과정' 카테고리의 다른 글
| [내일배움캠프 사전캠프] 20회차 TIL(7/22 수) - Docker, Docker-Compose (0) | 2026.07.22 |
|---|---|
| [내일배움캠프 사전캠프] 19회차 TIL(7/21 화) - Docker Image, Container (0) | 2026.07.21 |
| [내일배움캠프 사전캠프] 17회차 TIL(7/15 수) (0) | 2026.07.15 |
| [내일배움캠프 사전캠프] 16회차 TIL(7/14 화) (0) | 2026.07.14 |
| [내일배움캠프 사전캠프] 15회차 TIL(7/10 금) (0) | 2026.07.10 |