02a Controller·Mapping·DTO 📚 전체 맵 02c Entity·JPA·연관관계
Series 02 · Spring 기초 · 2/3

SESSION
SERVICE
REPOSITORY

HttpSession 동작 원리 · Service 계층이 왜 필요한가 · JPA Repository
1차 로그인부터 3차 Security까지 — 이 3개가 백엔드 핵심 흐름

02a Controller·Mapping·DTO 02b Session·Service·Repository 02c Entity·JPA·연관관계
01 Java 기초 02 Spring 기초 03 웹/통신 04 프로젝트 코드 05 심화 06 Security 07 JVM 08 DI
이 파일의 학습 목적
02b — HttpSession · Service 계층 · Repository의 역할

1차에서 로그인은 HttpSession으로 처리하고, 2·3차에서는 JWT로 바뀐다. 왜 바뀌는지 이해하려면 Session이 어떻게 동작하는지 먼저 알아야 한다. 02b는 1차~3차 백엔드의 핵심 흐름 — Controller가 Service를 부르고, Service가 Repository를 부르는 3계층 구조를 정리한다.

이 파일에서 배울 3가지
  • HttpSession — 서버 메모리 저장, JSESSIONID 쿠키 원리
  • Service 계층 — 왜 Controller에서 직접 Repository를 안 부르나
  • Repository + JPA — findBy·existsBy 메서드 자동 생성 원리
프로젝트 단계별 변화
  • · 1차 — session.setAttribute("loginUser", user)
  • · 2·3차 — Session 없이 JWT로 대체
  • · Service 3계층 — 1차~3차 전부 동일 구조 유지
시리즈 순서: 02a Controller·DTO → 02b ← 지금 여기 → 02c Entity·JPA·연관관계 → 03 웹/통신
01 — HttpSession 동작 원리
1차 로그인 방식 — 서버 메모리에 유저 정보 저장 · JSESSIONID 쿠키로 식별
🪪 세션이란? 1차 전용
🎰 비유 — 카지노 칩
입장 시 신분증 확인 → 카지노 칩(JSESSIONID) 지급
이후 칩만 보여주면 → 매번 신분증 확인 없이 통과
퇴장(로그아웃) → 칩 반납 → 서버에서 삭제
1
로그인 성공
session.setAttribute("loginUser", user)
→ 서버 메모리에 저장: {"JSESSIONID_abc123": {loginUser: User객체}}
2
브라우저에 쿠키 자동 저장
JSESSIONID=abc123 쿠키 → 브라우저가 자동으로 보관
3
이후 모든 요청에 쿠키 자동 첨부
→ 서버가 JSESSIONID로 메모리에서 유저 정보 찾음
session.getAttribute("loginUser") 가능
4
로그아웃
session.invalidate() → 서버 메모리에서 삭제 → 브라우저 쿠키도 만료
Java — 1차 프로젝트 세션 로그인 코드
// 로그인 처리
@PostMapping("/user/login")
public String login(@ModelAttribute UserDto userDto,
HttpSession session) {
User user = userService.login(userDto); // 검증
session.setAttribute("loginUser", user); // 세션 저장
return "redirect:/board/list";
}

// 로그인 상태 확인
@GetMapping("/board/write")
public String write(HttpSession session) {
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser == null) return "redirect:/user/login"; // 미로그인
return "board/write";
}

// 로그아웃
@GetMapping("/user/logout")
public String logout(HttpSession session) {
session.invalidate(); // 세션 전체 삭제
return "redirect:/user/login";
}
⚠️ 세션의 한계 → 2차에서 JWT로 전환한 이유
서버 재시작하면 메모리 초기화 → 로그인 풀림
서버가 여러 대일 때 세션 공유 문제 발생
2차부터 JWT 토큰 방식으로 전환 (토큰은 클라이언트가 보관)
02 — Service 계층이 왜 필요한가
Controller · Service · Repository — 역할 분리의 핵심
🍳 3계층 구조 Architecture
🍽️ 비유 — 레스토랑
Controller (웨이터) = 손님 응대, 주문 받기, 서빙
Service (요리사) = 실제 요리 (비즈니스 로직)
Repository (창고지기) = 재료 가져오기 (DB 조회)
요청
Browser
HTTP 요청
웨이터
Controller
요청/응답만
요리사
Service
비즈니스 로직
창고
Repository
DB 조회
저장소
DB
MariaDB
❌ Controller가 직접 DB 처리
@PostMapping("/user/login")
public String login(...) {
// Controller가 너무 많은 일을 함
User user = userRepository
.findByUsername(dto.getUsername())...
if (!encoder.matches(
dto.getPassword(),
user.getPassword())) ...
session.setAttribute("loginUser", user);
return "redirect:/board/list";
}
✅ Service로 분리
@PostMapping("/user/login")
public String login(...) {
// Controller는 요청/응답만
User user = userService
.login(dto.getUsername(),
dto.getPassword());
session.setAttribute(
"loginUser", user);
return "redirect:/board/list";
}
💡 Service 계층의 3가지 이유
1. 역할 분리 — Controller는 요청/응답, Service는 비즈니스 로직만
2. 재사용 — 같은 로직을 여러 Controller에서 호출 가능
3. 테스트 용이 — Service만 독립적으로 테스트 가능
03 — Repository + JPA 관계
인터페이스만 만들면 SQL 자동 생성 — JpaRepository의 마법
🗄️ JPA · Entity · Repository JPA
🤖 비유 — 자동 번역기
JPA = Java ↔ DB 자동 번역기
Java로 save(user) 하면 → JPA가 SQL INSERT INTO users ...로 번역해서 실행
SQL 직접 안 써도 됨
개념역할비유
EntityDB 테이블과 1:1 매핑되는 Java 클래스테이블 설계도
RepositoryDB 쿼리를 대신 해주는 인터페이스자동 SQL 번역기
JPAJava와 DB를 연결해주는 기술통역사
Java — UserRepository — 인터페이스만 만들면 끝
// 인터페이스만 만들면 JPA가 구현체를 자동 생성
public interface UserRepository extends JpaRepository<User, Long> {
// JpaRepository<엔티티타입, PK타입>

// ── 자동 제공 메서드 (직접 안 써도 됨) ──
// save(user) → INSERT or UPDATE
// findById(1L) → SELECT WHERE id=1
// findAll() → SELECT *
// delete(user) → DELETE
// count() → SELECT COUNT(*)

// ── 메서드 이름 규칙으로 쿼리 자동 생성 ──
Optional<User> findByUsername(String username);
// → SELECT * FROM users WHERE username = ?

boolean existsByUsername(String username);
// → SELECT COUNT(*) > 0 FROM users WHERE username = ?

List<Board> findByUserOrderByIdDesc(User user);
// → SELECT * FROM board WHERE user_id = ? ORDER BY id DESC
}
💡 메서드 이름 규칙 — JPA가 SQL로 자동 변환
findBy + 필드명 → WHERE 절
existsBy + 필드명 → COUNT > 0
OrderBy + 필드명 + Desc/Asc → ORDER BY
SQL 없이 Java 메서드 이름만으로 쿼리 생성 가능
Java — BoardRepository — 페이징 포함
public interface BoardRepository extends JpaRepository<Board, Long> {

// 전체 목록 페이징 조회
Page<Board> findAll(Pageable pageable);
// → SELECT * FROM board ORDER BY ... LIMIT ? OFFSET ?

// 특정 유저의 게시글 목록
List<Board> findByUser(User user);
// → SELECT * FROM board WHERE user_id = ?

// 제목 검색
Page<Board> findByTitleContaining(String keyword, Pageable pageable);
// → SELECT * FROM board WHERE title LIKE '%keyword%'
}
정리 — 전체 요청 흐름
1차 로그인 요청 한 개가 처리되는 순서
흐름 — POST /user/login 전체 처리 과정
// 1. 브라우저: form 제출 → POST /user/login (username=hong&password=1234)

// 2. Controller 수신
@PostMapping("/user/login")
public String login(@ModelAttribute UserDto dto, HttpSession session) {

// 3. Service 호출 (비즈니스 로직 위임)
User user = userService.login(dto.getUsername(), dto.getPassword());

// 6. 세션에 저장
session.setAttribute("loginUser", user);
return "redirect:/board/list";
}

// 4. Service 처리
public User login(String username, String password) {
User user = userRepository.findByUsername(username) // 5. Repository → DB
.orElseThrow(() -> new IllegalArgumentException("없는 유저"));

if (!encoder.matches(password, user.getPassword()))
throw new IllegalArgumentException("비밀번호 불일치");

return user; // Controller로 반환
}
02a Controller·Mapping·DTO 📚 전체 맵 02c Entity·JPA·연관관계