이 파일의 학습 목적
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 조회)
❌ Controller가 직접 DB 처리
@PostMapping("/user/login")
public String login(...) {
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(...) {
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만 독립적으로 테스트 가능
📌 우리 프로젝트 Service 목록
UserService — 회원가입·로그인·유저 조회 로직
BoardService — 게시글 CRUD·페이징·권한 확인 로직
CommentService — 댓글 CRUD 로직
03 — Repository + JPA 관계
인터페이스만 만들면 SQL 자동 생성 — JpaRepository의 마법
🗄️
JPA · Entity · Repository
JPA
🤖 비유 — 자동 번역기
JPA = Java ↔ DB 자동 번역기
Java로 save(user) 하면 → JPA가 SQL INSERT INTO users ...로 번역해서 실행
SQL 직접 안 써도 됨
| 개념 | 역할 | 비유 |
| Entity | DB 테이블과 1:1 매핑되는 Java 클래스 | 테이블 설계도 |
| Repository | DB 쿼리를 대신 해주는 인터페이스 | 자동 SQL 번역기 |
| JPA | Java와 DB를 연결해주는 기술 | 통역사 |
Java — UserRepository — 인터페이스만 만들면 끝
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
boolean existsByUsername(String username);
List<Board> findByUserOrderByIdDesc(User user);
}
💡 메서드 이름 규칙 — 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);
List<Board> findByUser(User user);
Page<Board> findByTitleContaining(String keyword, Pageable pageable);
}
📌 우리 프로젝트 Repository 목록
UserRepository — findByUsername, existsByUsername
BoardRepository — findAll(pageable), findByUser
CommentRepository — findByBoard
정리 — 전체 요청 흐름
1차 로그인 요청 한 개가 처리되는 순서
흐름 — POST /user/login 전체 처리 과정
@PostMapping("/user/login")
public String login(@ModelAttribute UserDto dto, HttpSession session) {
User user = userService.login(dto.getUsername(), dto.getPassword());
session.setAttribute("loginUser", user);
return "redirect:/board/list";
}
public User login(String username, String password) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new IllegalArgumentException("없는 유저"));
if (!encoder.matches(password, user.getPassword()))
throw new IllegalArgumentException("비밀번호 불일치");
return user;
}