IoC / DI
Spring을 이해하는 데 필요한 두 개념에 대해 알아보자.
IoC(Inversion of Control, 제어의 역전)
객체의 생성과 관리를 개발자가 아닌 Spring이 한다는 개념이다.
일반적인 Java 코드에서는 객체가 필요하면 new로 직접 만들지만, Spring에서는 new를 쓰지 않는다.
Spring이 알아서 객체를 만들어 필요한 곳에 넣어준다.
DI(Dependency Injection, 의존성 주입)
DI는 IoC를 구현하는 방식이다.
A 클래스가 B 클래스를 필요로 할 때, A가 직접 B를 만들지 않고 Spring이 B를 만들어서 A에 주입해준다.
코드를 비교해서 알아보자.
아래는 Spring없이 직접 객체를 만드는 방식이다.
public class ItemService {
private ItemRepository itemRepository = new ItemRepository(); // 직접 생성
}
아래는 Spring DI 방식이다.
// 생성자 주입
@Service
public class ItemService {
private final ItemRepository itemRepository;
public ItemService(ItemRepository itemRepository) { // Spring이 주입해줌
this.itemRepository = itemRepository;
}
}
이 방식에서 ItemService 는 ItemRepository 를 어떻게 만드는지 전혀 모른다.
그냥 생성자를 통해 받을 뿐이다. 이렇게 하면 나중에 테스트를 할 때 ItemRepository 자리에 가짜 객체를 넣기가 쉬워지고, 코드 간 결합도가 낮아진다.
DI 방식에는 세가지가 있다.
- 생성자 주입 (권장)
- 필드 주입
- Setter 주입
코드로 알아보자.
// 1. 생성자 주입 (권장)
@Service
public class ItemService {
private final ItemRepository itemRepository;
public ItemService(ItemRepository itemRepository) {
this.itemRepository = itemRepository;
}
}
// 2. 필드 주입
@Service
public class ItemService {
@Autowired
private ItemRepository itemRepository;
}
// 3. Setter 주입
@Service
public class ItemService {
private ItemRepository itemRepository;
@Autowired
public void setItemRepository(ItemRepository itemRepository) {
this.itemRepository = itemRepository;
}
}
필드 주입이 코드가 짧아 편해 보이지만 두 가지 문제가 있다.
첫째, final 을 붙일 수 없어서 객체가 불변임을 보장할 수 없다.
둘째, 테스트 코드 작성 시 가짜 객체를 주입하기가 어렵다.
Setter 주입은 객체 생성 후 나중에 의존성을 주입할 수 있어서 선택성 의존성이 필요한 경우에 사용한다.
예를 들어 특정 상황에서만 주입이 필요한 경우이다.
하지만 final 을 붙일 수 없고, Setter가 public이라 외부에서 언제든 바꿔버릴 수 있어서 객체의 불변성을 보장할 수 없다.
생성자 주입은 Lombok의 @RequiredArgsConstructor 를 쓰면 생성자 코드 없이도 동일하게 동작한다.
@Service
@RequiredArgsConstructor // final 필드에 대한 생성자 자동 생성
public class ItemService {
private final ItemRepository itemRepository;
}
Bean
Spring이 관리하는 객체를 Bean이라고 부른다.
@Component, @Service, @Repository, @Controler 가 붙은 클래스는 Spring이 시작될 때 자동으로 Bean에 등록한다.
위 네 가지 어노테이션의 기능은 동일하지만, 역할에 따라 구분해서 사용한다.
역할을 분리하는 이유는 코드의 책임을 명확하게 하기 위함이다. 컨트롤러가 직접 DB를 건드리거나, 레포지토리에 비즈니스 로직이 들어가면 나중에 유지보수가 어려워질 수 있다.
- @Controller : HTTP 요청을 받는 계층
- @Service : 비즈니스 로직을 처리하는 계층
- @Repository : DB와 통신하는 계층
- @Component : 위 세 가지에 속하지 않는 나머지
MySQL 연결
application.yml 설정
src/main/resources/application.yml 파일에 아래 내용을 쓰자. (기존 내용은 삭제해도 됨)
spring:
datasource:
url: jdbc:mysql://localhost:3306/myshop?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: create
show-sql: true
properties:
hibernate:
format_sql: true
server:
port: 8080
ddl-auto 옵션은 개발 단계에서는 create를 쓰고, 운영 환경에서는 반드시 none 또는 validate로 변경해야 한다.
create를 운영에서 쓰면 서버 재시작 시 데이터가 전부 날아간다.
show-sql: true 옵션은 JPA가 어떤 SQL을 실행하는지 콘솔에 보여준다. 공부할 때는 켜두는 것이 좋다고 한다.
내가 짠 코드가 어떤 쿼리로 변환되는지 눈으로 확인할 수 있다.
Docker로 MySQL 띄우기
MySQL을 로컬에 직접 설치하지 않고 Docker로 띄우는 과정이다.
Docker Desktop이 실행 중인 상태에서 터미널에 아래 명령어를 입력하자.
docker run -d \
--name mysql-local \
-e MYSQL_ROOT_PASSWORD=1234 \
-e MYSQL_DATABASE=myshop \
-p 3306:3306 \
mysql:8
실행 후 docker ps 명령어 입력해서 컨테이너가 동작 중인지 확인할 수 있다.

이제 MyshopApplication.java 를 실행할 수 있다.
Intellij에서 실행버튼을 눌러보면

콘솔에 여러 줄이 나오고 마지막에 이런 문구가 나오면 성공이다.
아직 아무 API도 만들지 않았기 때문에 http://localhost:8080 에 접속하면 에러 페이지가 뜨는데 이건 정상이다.
'Backend > Spring' 카테고리의 다른 글
| [Backend/Spring] Spring Boot - @OneToMany 와 N+1 문제 (0) | 2026.03.29 |
|---|---|
| [Backend/Spring] Spring Boot - JPA 연관관계 (0) | 2026.03.29 |
| [Backend/Spring] Spring Boot - JPA와 MySQP 연동 (0) | 2026.03.29 |
| [Backend/Spring] Spring Boot - MVC 계층 구조와 API (0) | 2026.03.29 |
| [Backend/Spring] Spring Boot - 프로젝트 생성과 구조 이해 (0) | 2026.03.29 |