이 파일의 학습 목적
01c — Lambda · Stream · @Transactional — Java 기초 마지막
Service 코드를 보면 .stream().map(this::toDto).collect(...) 같은 표현이 반복된다.
화살표 ->가 등장하고, @Transactional(readOnly=true)도 어디서나 보인다.
01c는 Java 기초 시리즈의 마무리 — Lambda·Stream·@Transactional 3가지를 정리한다.
이 파일에서 배울 3가지
- ① Lambda —
() -> 문법, 메서드 레퍼런스 ::
- ② Stream — .stream().filter().map().collect() 파이프라인
- ③ @Transactional — 더티체킹, readOnly, 롤백 조건
프로젝트 코드에서 등장 위치
- ·
.orElseThrow(() -> new ...) — 모든 Service
- ·
.stream().map(this::toDto) — BoardService·AdminService
- ·
@Transactional(readOnly=true) — 심화 STEP 4
시리즈 순서:
01a → 01b →
01c ← 지금 여기
→ 02a Spring @Controller·DTO
→ 02b Session·Service·Repository
01 — Lambda
Java 8부터 도입 — 간단한 함수를 한 줄로 표현하는 방식
⚡
Lambda란?
Java 8+
✏️ 비유 — 메모 vs 정식 문서
기존 방식 = 정식 문서 — 이름·도장·형식 다 갖춰야 함
Lambda = 메모 — 핵심 내용만 간결하게 적음
→ 이름 없는 함수(익명 함수)를 한 줄로 표현
❌ 기존 방식 (익명 클래스)
Comparator<User> comp =
new Comparator<User>() {
@Override
public int compare(User a, User b) {
return a.getId()
.compareTo(b.getId());
}
};
✅ Lambda (한 줄로 끝)
Comparator<User> comp =
(a, b) -> a.getId()
.compareTo(b.getId());
Java — Lambda 문법 패턴
(파라미터) -> 실행할 코드
() -> System.out.println("실행")
user -> user.getUsername()
(a, b) -> a.getId().compareTo(b.getId())
user -> {
String name = user.getUsername();
return name.toUpperCase();
}
📌 우리 프로젝트에서 Lambda가 등장하는 곳
.orElseThrow(() -> new IllegalArgumentException("없는 유저"))
.map(board -> new BoardDto(board))
.filter(user -> user.getRole().equals("ROLE_ADMIN"))
02 — Stream
목록 데이터를 파이프라인으로 처리 — 변환·필터·수집을 한 줄로
🚰
Stream이란?
Java 8+
🏭 비유 — 공장 컨베이어 벨트
원재료(List) → 벨트에 올림(.stream()) → 가공(.map()) → 선별(.filter()) → 포장(.collect())
→ 각 단계가 파이프라인처럼 연결됨
→ 원본 List는 변하지 않음 — 새로운 결과물이 생성됨
→
→
변환
.map()
Board → BoardDto
→
Java — Stream 실제 프로젝트 패턴
List<BoardDto> dtoList = boardList.stream()
.map(board -> new BoardDto(board))
.collect(Collectors.toList());
List<User> admins = userList.stream()
.filter(user -> user.getRole().equals("ROLE_ADMIN"))
.collect(Collectors.toList());
Page<Board> boards = boardRepository.findAll(pageable);
List<BoardDto> dtoList = boards.getContent().stream()
.map(BoardDto::new)
.collect(Collectors.toList());
| Stream 메서드 | 하는 일 | 프로젝트 예시 |
| .map() | 각 요소를 변환 | .map(board -> new BoardDto(board)) |
| .filter() | 조건에 맞는 것만 선택 | .filter(u -> u.getRole().equals("ROLE_ADMIN")) |
| .collect() | 결과를 List/Set 등으로 수집 | .collect(Collectors.toList()) |
| .forEach() | 각 요소에 작업 실행 | .forEach(u -> System.out.println(u)) |
| .findFirst() | 첫 번째 요소 반환 (Optional) | .findFirst().orElse(null) |
| .count() | 요소 개수 반환 | .count() |
| .sorted() | 정렬 | .sorted(Comparator.comparing(Board::getId)) |
💡 메서드 참조 (::) 표현
.map(board -> new BoardDto(board))
= .map(BoardDto::new) — 생성자 참조
.forEach(u -> System.out.println(u))
= .forEach(System.out::println) — 메서드 참조
→ Lambda를 더 짧게 쓰는 방식. 읽히면 OK, 못 읽히면 Lambda로 써도 됨
03 — @Transactional + 더티체킹
DB 작업의 묶음 처리 · save() 없이 자동 UPDATE — 프로젝트 수정 기능 전체에 적용
🏦
@Transactional이란?
Spring + JPA
🏦 비유 — 은행 송금
A계좌에서 돈 빼기 → B계좌에 돈 넣기
이 두 작업은 반드시 같이 성공하거나 같이 실패해야 함
중간에 오류 → A계좌에서만 빠지고 B계좌엔 안 들어오는 사태 발생
@Transactional = "이 두 작업은 한 묶음이야"
→ 중간에 오류나면 전부 취소 (롤백)
→ 다 성공하면 전부 저장 (커밋)
Java — @Transactional 기본 동작
@Transactional
public void transfer(Long fromId, Long toId, int amount) {
Account from = accountRepository.findById(fromId).get();
from.setBalance(from.getBalance() - amount);
Account to = accountRepository.findById(toId).get();
to.setBalance(to.getBalance() + amount);
}
Java — 더티체킹 — save() 없이 자동 UPDATE ⭐
@Transactional
public void update(Long id, BoardDto dto) {
Board board = boardRepository.findById(id).get();
board.setTitle(dto.getTitle());
board.setContent(dto.getContent());
}
⚠️ 핵심 주의사항
@Transactional 없이 더티체킹을 쓰면 → 변경이 DB에 반영되지 않음
수정 기능에는 반드시 @Transactional 필요!
조회만 할 때는 → @Transactional(readOnly = true) → 성능 최적화
📌 우리 프로젝트에서 — BoardService.java
update() 메서드 — @Transactional + 더티체킹 → save() 없이 UPDATE
findAll() 메서드 — @Transactional(readOnly = true) → 조회 최적화
Java 기초 시리즈 — 전체 정리
01a · 01b · 01c — 이것만 알면 프로젝트 코드가 읽힌다
01a
어노테이션·클래스·접근제어자
@기호로 메모 → Spring 자동처리
interface/class 구분
public/private 접근 범위
01b
Lombok·Optional·Generic
@RequiredArgsConstructor → DI
Optional → null 안전
<T> → 범용 타입
01c
Lambda·Stream·@Transactional
() -> 함수 한 줄 표현
.stream().map().collect()
DB 묶음처리 + 더티체킹
Java — 01 시리즈 핵심 총집합 — 실제 프로젝트 코드
@RestController
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
@GetMapping("/api/boards")
public ResponseEntity<?> list(...) {
Page<Board> boards = boardService.findAll(pageable);
List<BoardDto> dtoList = boards.getContent()
.stream()
.map(board -> new BoardDto(board))
.collect(Collectors.toList());
return ResponseEntity.ok(dtoList);
}
}
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
@Transactional
public void update(Long id, BoardDto dto) {
Board board = boardRepository.findById(id)
.orElseThrow(() ->
new IllegalArgumentException("없는 게시글"));
board.setTitle(dto.getTitle());
}
}