유효성 검사
반드시 DTO로 받아야 한다.
로그인, 회원가입 실습
@NotNull, @Valid, @Size, @Pattern
유효성 검사는 필수!
아래 어노테이션이 붙은 클래스는 자동으로 Bean 등록
name이 주어지지 않으면 클래스 이름의 첫자를 소문자로 한 이름을사용
@RestController("name") // 컨트롤러 객체 생성(요청 처리 담당)
@Service("name") // 서비스 객체 생성(비즈니스 로직 처리 담당)
@Repository("name") // DAO 객체 생성 (DB 연동 처리 담당)
@Component("name") // 기타 기능을 제공하는 객체 생성
@Mapper // MyBatis 매퍼 객체 생성 (DB 연동 처리 담당)
@Bean이 적용된 메소드 이름이 id(name)이 되고, 리턴 객체가 Bean이 된다.
@Configuration이 적용된 클래스 내에서 작동
@Configuration
public class AppConfig {
public BeanType getBean() {
return new BeanType();
}
}
선언적 방식은 내가 직접 작성한 클래스일 때 사용하고, 프로그램 방식은 내가 바꿀 수 없는 외부 라이브러리나 클래스를 사용할 때 사용한다. 보통 전자를 훨씬 많이 사용함.
이미 외부에서 만들어져 있는 것을 가져다가 쓰는 방식
주입 대상
@Service, @Repository, @Component가 적용된 클래스의 빈@ Configuration 클래스의 @Bean이 적용된 메소드의 반환 빈필드 주입: @Autowired 이용
생성자 주입: 매개변수 이용해서 빈 주입
객체가 필요한 다른 객체를 직접 만들지 않고, 외부에서 주입받는 것
// ❌ DI 없이 직접 생성
public class DiMemberService {
private DiAComponent aComponent = new DiAComponent(); // 직접 생성
}
// ✅ DI 사용 - Spring이 만들어서 넣어줌
public class DiMemberService {
@Autowired
private DiAComponent aComponent; // Spring이 주입
}
왜 좋냐면? 결합도가 낮아져서 나중에 구현체를 바꿔도 DiMemberService 코드를 안 건드려도 됨.
@Component / @Service → Bean 등록Spring 컨테이너: "이 클래스들은 내가 관리할 객체(Bean)야!"
| 어노테이션 | 의미 |
|---|---|
@Component | 일반 컴포넌트 |
@Service | 서비스 계층 |
둘 다 Spring Bean으로 등록된다는 점은 동일.
@Autowired → 주입Spring이 타입을 보고 맞는 Bean을 자동으로 꽂아줌.
@Qualifier → 구분자같은 타입의 Bean이 2개 이상일 때 어떤 걸 쓸지 이름으로 지정.
@Qualifier가 필요한가// DiInterface를 구현한 클래스가 2개!
@Component DiInterfaceImplA implements DiInterface
@Component DiInterfaceImplB implements DiInterface
@Autowired
private DiInterface impl; // ← Spring: "A 줄까? B 줄까? 모르겠는데!" → 💥 오류
그래서 @Qualifier로 이름을 지정해줘야 함.
Bean 이름은 기본적으로 클래스명 앞글자 소문자 → diInterfaceImplA, diInterfaceImplB
@Autowired 필드에 직접)@Autowired
private DiAComponent aComponent;
@Autowired @Qualifier("diInterfaceImplA")
private DiInterface implA;
private DiBComponent bComponent;
private DiInterface implB;
public DiMemberService(DiBComponent bComponent, @Qualifier("diInterfaceImplB") DiInterface implB) {
this.bComponent = bComponent;
this.implB = implB;
}
join() 실행 흐름public void join() {
log.info("실행"); // DiMemberService.join 실행됨
aComponent.method(); // DiAComponent의 method()
bComponent.method(); // DiBComponent의 method()
implA.method(); // DiInterfaceImplA의 method() → "실행" 로그
implB.method(); // DiInterfaceImplB의 method() → "실행" 로그
}
private DiInterface implA; // DiInterfaceImplA 타입이 아니라 인터페이스 타입으로 받음
나중에 ImplA → ImplC로 교체해도 DiMemberService 코드를 전혀 안 바꿔도 됨.
Bean만 바꾸면 끝 → 이게 DI + 인터페이스의 핵심 장점!
properties 작성 위치
application.properties에서 직접 작성
String Bean 객체 내부에 주입: @Value
필드, 생성자 매개변수에 적용할 수 있음.
@Value(”${api.key}”) private String key;
계층 간 데이터를 전달하기 위한 객체.
Spring MVC 계층 구조:
Client ↔ Controller ↔ Service ↔ Repository ↔ DB
Entity(DB 테이블과 매핑된 객체)를 그대로 노출하면 문제가 생긴다:
→ DTO를 따로 만들어 필요한 데이터만 담아 전달한다.
// Entity — DB 테이블과 매핑
@Entity
public class User {
private Long id;
private String username;
private String password; // 절대 외부에 노출하면 안 됨
private String email;
private LocalDateTime createdAt;
}
// DTO — 클라이언트에 전달할 데이터만
public class UserResponseDto {
private Long id;
private String username;
private String email;
// password, createdAt은 제외
}
| Entity | DTO | |
|---|---|---|
| 목적 | DB 매핑 | 계층 간 데이터 전달 |
| 필드 | 테이블 전체 컬럼 | 필요한 것만 |
| 노출 | 외부 노출 X | 외부 전달용 O |
Client → [UserRequestDto] → Controller → Service → Repository → DB
Client ← [UserResponseDto] ← Controller ← Service ← Repository ← DB
요청용(RequestDto)과 응답용(ResponseDto)을 분리해서 만드는 것이 일반적이다.
@Getter, @Setter, @AllArgsConstructor 등으로 보일러플레이트 제거Entity는 DB용, DTO는 통신용