⏰💡 마이페이지 기능 구현

2026. 3. 22. 16:40·프로젝트/스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트

1️⃣ 왜 이 기능이 필요한가?

우리가 만든 서비스는 강의를 신청하는 시스템이다.

그렇다면 사용자는 자연스럽게 이런 생각을 한다:

  • “내가 어떤 강의를 신청했지?”
  • “신청 상태는 뭐지?”
  • “다시 확인하고 싶은데 어디서 보지?”

👉 그래서 필요한 기능이 바로

“내 신청 내역 (마이페이지)” 이다.


2️⃣ 전체 흐름 먼저 이해하기 (중요🔥)

이 기능은 단순히 DB 조회가 아니라 전체 흐름을 이해하는 게 핵심이다.

[사용자]
   ↓ 클릭
"내 강의장" 버튼
   ↓
/me 페이지 이동
   ↓
JS가 API 호출
(GET /api/me/enrollments)
   ↓
Controller
   ↓
Service
   ↓
Repository (DB 조회)
   ↓
DTO 변환
   ↓
JSON 응답
   ↓
화면에 카드로 렌더링

👉 초보자 기준 핵심 포인트:

  • 화면(HTML) + API + DB가 연결된 기능이다

3️⃣ DB 구조 이해하기

우리는 이미 이런 구조를 가지고 있다.

📌 Enrollment (신청 테이블)

  • userId (누가)
  • lecture (어떤 강의)
  • createdAt (언제 신청)

👉 즉

👉 “userId 기준으로 조회하면 = 내 신청 목록”

이걸 이용하면 된다.


4️⃣ Repository에서 조회 기능 만들기

📌 위치: EnrollmentRepository

@Query("select e from Enrollment e join fetch e.lecture where e.userId = :userId order by e.createdAt desc")
List<Enrollment> findByUserIdWithLecture(@Param("userId") Long userId);

🔥 왜 join fetch를 쓰는가?

초보자들이 가장 많이 놓치는 부분이다.

❌ 그냥 조회하면

  • Enrollment 가져옴
  • Lecture는 나중에 또 쿼리 발생 (N+1 문제)

✅ join fetch 사용하면

  • 한 번에 다 가져옴 (성능 GOOD)

5️⃣ DTO 만들기

왜 DTO를 만들까?

👉 Entity를 그대로 보내면 위험하고 불필요한 데이터도 포함됨

그래서 필요한 데이터만 담는다.

@Getter
@Builder
@AllArgsConstructor
public class MyEnrollmentResponse {

    private Long lectureId;
    private String lectureTitle;
    private int capacity;
    private int enrolledCount;
    private String status;

    public static MyEnrollmentResponse from(Enrollment e) {
        Lecture l = e.getLecture();

        return MyEnrollmentResponse.builder()
                .lectureId(l.getId())
                .lectureTitle(l.getTitle())
                .capacity(l.getCapacity())
                .enrolledCount(l.getEnrolledCount())
                .status(l.getStatus().name())
                .build();
    }
}

💡 핵심 포인트

👉 DTO는 “프론트에 보여줄 데이터만” 담는다


6️⃣ Service 구현

📌 위치: MyPageService

@Service
@RequiredArgsConstructor
public class MyPageService {

    private final EnrollmentRepository enrollmentRepository;

    @Transactional(readOnly = true)
    public List<MyEnrollmentResponse> getMyEnrollments(Long userId) {
        return enrollmentRepository.findByUserIdWithLecture(userId)
                .stream()
                .map(MyEnrollmentResponse::from)
                .toList();
    }
}

💡 초보자 포인트

  • readOnly = true → 조회 성능 최적화
  • stream().map() → DTO 변환

7️⃣ Controller 구현

📌 API: GET /api/me/enrollments

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/me")
public class MyPageApiController {

    private final MyPageService myPageService;

    private Long currentUserId() {
        CustomPrincipal principal = (CustomPrincipal) SecurityContextHolder
                .getContext()
                .getAuthentication()
                .getPrincipal();
        return principal.getUserId();
    }

    @GetMapping("/enrollments")
    public ResponseEntity<List<MyEnrollmentResponse>> myEnrollments() {
        Long userId = currentUserId();
        return ResponseEntity.ok(myPageService.getMyEnrollments(userId));
    }
}

🔥 핵심 포인트

👉 SecurityContextHolder에서 userId 꺼내는 구조

이게 JWT 기반 인증의 핵심이다.


8️⃣ View (페이지) 연결 및 lobby.html 수정

📌 /me 페이지를 만들어야 한다

@GetMapping("/me")
public String me() {
    return "lecture/me";
}
<a sec:authorize="isAuthenticated()"
   href="/me"
   class="bg-slate-900 text-white px-5 py-2.5 rounded-xl">
    내 강의장
</a>

💡 초보자 포인트

  • sec:authorize="isAuthenticated()"
  • 👉 로그인한 사람만 보이게 하는 기능

9️⃣ 프론트에서 API 호출

const res = await fetch("/api/me/enrollments", {
  headers: {
    Authorization: "Bearer " + localStorage.getItem("access_token")
  }
});

💡 핵심

👉 JWT는 반드시 헤더에 넣어야 한다

🔟 결과 화면

  • 강의 제목
  • 정원
  • 상태
  • 신청일

👉 사용자 입장에서 “내 강의 목록” 완성

'프로젝트 > 스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트' 카테고리의 다른 글

⏰💡 관리자 기능 + UI (JWT Role기반)  (0) 2026.03.22
⏰💡 오픈 시간(openAt) 전에는 신청 버튼 막기 + 카운트다운 표시  (0) 2026.03.07
⏰🚨 “입장권이 있는데도 apply 페이지에서 입장권 없음 오류 발생”  (0) 2026.03.06
⏰🚨 선착순 강의 신청에서 “이미 신청했는데 다시 신청 가능?” 문제 해결  (0) 2026.03.06
⏰💡 선착순 강의 Redis 대기열(ZSET) + 입장권 원자화 + 소비(consume) 적용하기 (Lua 1개로 끝)  (0) 2026.02.26
'프로젝트/스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트' 카테고리의 다른 글
  • ⏰💡 관리자 기능 + UI (JWT Role기반)
  • ⏰💡 오픈 시간(openAt) 전에는 신청 버튼 막기 + 카운트다운 표시
  • ⏰🚨 “입장권이 있는데도 apply 페이지에서 입장권 없음 오류 발생”
  • ⏰🚨 선착순 강의 신청에서 “이미 신청했는데 다시 신청 가능?” 문제 해결
hak0622
hak0622
개발하면서 “이게 뭐지?”라는 순간마다 궁금한 점을 바탕으로 정리한 개발 블로그입니다.
  • hak0622
    궁금한 개발 이야기 Why?
    hak0622
  • 전체
    오늘
    어제
    • 분류 전체보기 (68)
      • 공부 (36)
        • 1. 자바 ORM 표준 JPA 프로그래밍 - 기본.. (35)
        • 시험 (1)
      • 프로젝트 (32)
        • 스프링 부트 3 백엔드 개발자 되기 Blog + .. (32)
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
hak0622
⏰💡 마이페이지 기능 구현
상단으로

티스토리툴바