01b Lombok·Optional·Generic 📚 전체 맵 02a Spring 기초 시작
Series 01 · Java 기초 · 3/3

LAMBDA
STREAM
TRANSACTIONAL

간결한 함수 표현 · 데이터 파이프라인 처리 · DB 묶음 처리
Java 기초 시리즈 마지막 — 프로젝트 Service 코드 전체에서 등장

01a 어노테이션·클래스·접근제어자 01b Lombok·Optional·Generic 01c Lambda·Stream·@Transactional
01 Java 기초 02 Spring 기초 03 웹/통신 04 프로젝트 코드 05 심화 06 Security 07 JVM 08 DI
이 파일의 학습 목적
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();
}
02 — Stream
목록 데이터를 파이프라인으로 처리 — 변환·필터·수집을 한 줄로
🚰 Stream이란? Java 8+
🏭 비유 — 공장 컨베이어 벨트
원재료(List) → 벨트에 올림(.stream()) → 가공(.map()) → 선별(.filter()) → 포장(.collect())
→ 각 단계가 파이프라인처럼 연결됨
→ 원본 List는 변하지 않음 — 새로운 결과물이 생성됨
원본
List<Board>
boardList
시작
.stream()
스트림으로 변환
변환
.map()
Board → BoardDto
수집
.collect()
List로 모음
Java — Stream 실제 프로젝트 패턴
// 패턴 1: Board 목록 → BoardDto 목록으로 변환 (프로젝트 전체에서 반복)
List<BoardDto> dtoList = boardList.stream()
.map(board -> new BoardDto(board)) // 각 Board를 BoardDto로
.collect(Collectors.toList()); // List로 수집

// 패턴 2: ROLE_ADMIN인 유저만 필터링
List<User> admins = userList.stream()
.filter(user -> user.getRole().equals("ROLE_ADMIN"))
.collect(Collectors.toList());

// 패턴 3: Page<Board> → List<BoardDto> (페이징 결과 변환)
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); // A 출금

// 여기서 예외 발생하면 → ③ 롤백 (A 출금도 취소됨)

Account to = accountRepository.findById(toId).get();
to.setBalance(to.getBalance() + amount); // B 입금

// ② 모두 성공 → 커밋 (DB에 반영)
}
Java — 더티체킹 — save() 없이 자동 UPDATE ⭐
@Transactional
public void update(Long id, BoardDto dto) {
Board board = boardRepository.findById(id).get();
// JPA가 트랜잭션 시작 시 board 상태를 스냅샷으로 저장

board.setTitle(dto.getTitle()); // 변경
board.setContent(dto.getContent()); // 변경

// boardRepository.save(board); ← 이거 없어도 됨!

// @Transactional 끝날 때 JPA가 스냅샷과 현재 상태 비교
// 변경된 부분 감지 → 자동으로 UPDATE 실행
// UPDATE board SET title=?, content=? WHERE id=?
}
⚠️ 핵심 주의사항
@Transactional 없이 더티체킹을 쓰면 → 변경이 DB에 반영되지 않음
수정 기능에는 반드시 @Transactional 필요!

조회만 할 때는 → @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 // 01a: 어노테이션
@RequiredArgsConstructor // 01b: Lombok → DI
public class BoardController {

private final BoardService boardService; // 01a: 접근제어자

@GetMapping("/api/boards")
public ResponseEntity<?> list(...) { // 01b: Generic

Page<Board> boards = boardService.findAll(pageable);

List<BoardDto> dtoList = boards.getContent()
.stream() // 01c: Stream
.map(board -> new BoardDto(board)) // 01c: Lambda
.collect(Collectors.toList());

return ResponseEntity.ok(dtoList);
}
}

@Service
@RequiredArgsConstructor
public class BoardService {

private final BoardRepository boardRepository;

@Transactional // 01c: @Transactional
public void update(Long id, BoardDto dto) {
Board board = boardRepository.findById(id)
.orElseThrow(() -> // 01b: Optional
new IllegalArgumentException("없는 게시글"));
board.setTitle(dto.getTitle());
// save() 없음 — 더티체킹 자동 UPDATE
}
}
01b Lombok·Optional·Generic 📚 전체 맵 02a Spring 기초 시작