이 파일의 학습 목적
01b — Lombok · Optional · Generic을 왜 배우나?
프로젝트 코드를 열면 @Getter @Setter @RequiredArgsConstructor가 모든 클래스 위에 붙어 있다.
.orElseThrow()는 Repository 호출마다 등장하고, Page<Board> 같은 꺾쇠 표현도 어디서나 나온다.
01b는 이 3가지를 한 번에 정리한다.
이 파일에서 배울 3가지
- ① Lombok — @Getter·@Setter·@Builder·@RequiredArgsConstructor
- ② Optional — null 안전 처리·orElseThrow()·ifPresent()
- ③ Generic <T> — 꺾쇠 의미·Page<Board>·List<BoardDto>
프로젝트 코드에서 등장 위치
- ·
@RequiredArgsConstructor — 모든 Service·Controller
- ·
.orElseThrow() — Repository 조회 전체
- ·
Page<Board>, List<BoardDto> — BoardService
시리즈 순서:
01a 어노테이션·클래스 →
01b ← 지금 여기
→ 01c Lambda·Stream
→ 02 Spring 기초
01 — Lombok
반복적으로 써야 하는 코드(getter·setter·생성자)를 어노테이션으로 자동 생성
⚙️
Lombok이란?
Library
❌ Lombok 없이 (직접 다 작성)
public class User {
private Long id;
private String username;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String u) {
this.username = u;
}
}
✅ Lombok 사용 (어노테이션 하나면 끝)
@Getter
@Setter
public class User {
private Long id;
private String username;
}
| 어노테이션 | 자동 생성 내용 |
| @Getter | 모든 필드의 getXxx() 메서드 |
| @Setter | 모든 필드의 setXxx() 메서드 |
| @NoArgsConstructor | 파라미터 없는 기본 생성자 User() |
| @AllArgsConstructor | 모든 필드를 받는 생성자 User(id, username, ...) |
| @RequiredArgsConstructor | final 필드만 받는 생성자 (DI에 사용) |
| @ToString | toString() 메서드 (출력용) |
| @Builder | 빌더 패턴 — User.builder().id(1).build() |
| @Data | @Getter+@Setter+@ToString+@EqualsAndHashCode 한번에 |
Java — @RequiredArgsConstructor — DI의 핵심 ⭐
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
}
💡 @RequiredArgsConstructor + final = DI의 황금 조합
private final UserService userService 한 줄이면
Lombok이 생성자 만들고 → Spring이 UserService 빈 찾아서 주입.
1차~3차 프로젝트 모든 Controller·Service에서 이 패턴이 반복됨.
📌 우리 프로젝트에서
UserController — @RequiredArgsConstructor + private final UserService userService
UserService — @RequiredArgsConstructor + private final UserRepository userRepository
User Entity — @Getter @NoArgsConstructor @Builder
02 — Optional
값이 있을 수도 없을 수도 있는 상황을 안전하게 처리 — NullPointerException 방지
🎁
Optional이란?
Null Safety
🎁 비유 — 선물 상자
Optional = 선물 상자
→ 상자 안에 선물(값)이 있을 수도, 없을 수도 있음
→ 열어보기 전까진 모름
Optional 없이 → 값이 없는데 바로 쓰면 NullPointerException 발생 → 프로그램 터짐
Optional 쓰면 → 값이 없을 때 대응 방법을 강제로 작성하게 됨 → 안전한 코드
❌ Optional 없이 (위험)
User user = userRepository
.findByUsername("hong");
user.getPassword();
✅ Optional 사용 (안전)
Optional<User> optional =
userRepository.findByUsername("hong");
User user = optional
.orElseThrow(() ->
new IllegalArgumentException(
"없는 유저"));
Java — Optional 3가지 처리 방법
Optional<User> optional = userRepository.findByUsername("hong");
User user = optional.orElseThrow(
() -> new IllegalArgumentException("없는 유저입니다.")
);
optional.ifPresent(u -> {
request.setAttribute("loginUser", u);
});
User user = optional.orElse(null);
⚠️ orElse(null) 주의
orElse(null)은 결국 null을 반환할 수 있어서 Optional을 쓰는 의미가 없어짐.
가능하면 orElseThrow()로 명시적으로 예외를 던지는 게 좋음.
📌 우리 프로젝트에서 — UserService.java
userRepository.findByUsername(username).orElseThrow(...)
→ DB에서 유저 못 찾으면 예외 던져서 GlobalExceptionHandler가 처리
03 — Generic <T>
타입을 나중에 정하는 것 — 어떤 타입이든 담을 수 있는 범용 상자
📦
Generic이란?
Type Safety
📦 비유 — 범용 택배 상자
Generic 없이 → 사과 상자, 책 상자, 옷 상자 → 종류마다 별도 상자 필요
Generic 있으면 → 어떤 물건이든 담을 수 있는 범용 상자 하나
→ 상자 열 때 "이건 사과 담는 상자야" 라고 미리 알려주는 것이 <T>
❌ Generic 없이 (타입마다 별도 클래스)
class UserList { User[] items; }
class BoardList { Board[] items; }
class DtoList { BoardDto[] items; }
✅ Generic 사용 (하나로 해결)
class Page<T> { T[] items; }
Page<User> userPage;
Page<Board> boardPage;
Page<BoardDto> dtoPage;
Java — 프로젝트에서 자주 보이는 Generic 표현들
Optional<User> optional = userRepository.findByUsername(username);
Page<Board> boards = boardRepository.findAll(pageable);
List<BoardDto> dtoList = boards.getContent().stream()...
ResponseEntity<?> response = ResponseEntity.ok(dtoList);
Map<String, String> result = Map.of("token", token, "role", role);
| 표현 | 의미 | 프로젝트 사용처 |
| Optional<User> | User가 있을 수도 없을 수도 | userRepository.findByUsername() |
| Page<Board> | Board 목록 + 페이징 정보 | boardRepository.findAll(pageable) |
| List<BoardDto> | BoardDto 목록 | stream().map().collect() |
| ResponseEntity<?> | 어떤 타입이든 반환 | Controller 반환 타입 |
| Map<String, String> | 문자열 키-값 쌍 | JWT 토큰+role 반환 |
💡 <?> 와 <T> 차이
<T> → 클래스/메서드 선언할 때 씀. "나중에 타입 정할게"
<?> → 사용할 때 씀. "어떤 타입이든 상관없어" (와일드카드)
→ 프로젝트에서는 주로 <?>가 ResponseEntity에서 보임
정리 — 핵심 연결
Lombok · Optional · Generic — 프로젝트에서 이렇게 함께 쓰임
Java — 실제 프로젝트 코드에서 3가지 모두 등장
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public ResponseEntity<?> login(LoginDto dto) {
User user = userRepository
.findByUsername(dto.getUsername())
.orElseThrow(() ->
new IllegalArgumentException("없는 유저"));
Map<String, String> result =
Map.of("token", token, "role", role);
return ResponseEntity.ok(result);
}
}
⚙️
Lombok
@RequiredArgsConstructor
→ 생성자 자동생성
→ Spring DI 연결
🎁
Optional
findByUsername() 반환값
→ orElseThrow()로 처리
→ null 안전
📦
Generic
List<BoardDto>
ResponseEntity<?>
Map<String,String>