수업 복습

Spring MVC / JSP Q&A 정리

_김영인 2026. 1. 22. 10:08

 

1) JSP 페이지를 WEB-INF 하위에 두는 이유

 

결론

 

JSP를 브라우저가 직접 호출하지 못하게 막고 반드시 Controller (또는 ViewResolver) 경유로만 렌더링되게 만들기 위해서다.

 

왜 중요한가

 

/WEB-INF/ 아래 리소스는 컨테이너 (톰캣) 규칙상 외부 URL로 직접 접근이 불가능하다.
그래서 사용자가 주소창에 .../WEB-INF/views/main.jsp처럼 입력해도 접근이 차단 (보통 404 / 접근불가 처리) 된다.

 

효과

  • 보호 (은닉): JSP를 “직접 실행 가능한 엔드포인트”처럼 노출하지 않는다.
  • 안정성: URL 우회 접근, 임의 JSP 호출 같은 패턴을 예방한다.
  • MVC 강제: View는 반드시 Controller → Model → ViewResolver → JSP 흐름을 타게 된다.
  • 결과적으로 “JSP는 화면 템플릿이고 URL 엔드포인트는 Controller가 책임진다”가 구조적으로 고정된다.

정리: JSP를 숨기고 URL은 Controller가 책임진다.


 

2) pom.xml이 있어야 JSTL을 쓸 수 있는 이유

결론

pom.xml의 dependencies로 JSTL/JSP 관련 라이브러리(JAR)를 포함해야 JSTL이 동작한다.

 

왜 pom.xml이 등장하나

 

Maven 기반 프로젝트 (Spring Boot 포함)는 필요한 라이브러리를 pom.xml에 선언하면 Maven이 다운로드해 classpath에 포함시킨다.

 

JSTL을 쓰려면 대표적으로

  • JSTL 라이브러리
  • JSP 컴파일 / 실행 관련 라이브러리 (환경에 따라)
    가 필요하다.

<c:forEach> 같은 태그는 “JSP 코드만으로 자동 실행되는 기능”이 아니라 이를 해석해 실행할 JAR 의존성이 필요하다.

 

정리: pom.xml은 의존성 (dependency) 관리의 핵심 파일이고 JSTL은 해당 라이브러리가 있어야 사용 가능하다.


 

3) POST 방식을 GET으로 바꾸려면 어떻게 해야 하나

 

결론

  • 컨트롤러에서 @PostMapping을 @GetMapping으로 바꾸면 GET 요청을 처리할 수는 있다.
  • 하지만 실제로 서버에 들어오는 요청 메서드는 클라이언트가 결정한다.

HTML이 <form method="POST">면 요청은 무조건 POST다.
GET으로 요청이 들어오게 려면 form의 method 자체를 GET으로 바꾸거나 링크 / 주소창 요청처럼 GET으로 보내야 한다.

 

학습 / 테스트용 깔끔한 방식

 

POST / GET을 둘 다 허용하고 싶으면 예를 들어:

  • @RequestMapping(value="/login", method={RequestMethod.GET, RequestMethod.POST})
    또는
  • GET / POST를 메서드로 분리해서 의도를 명확히 한다.

 

주의점

 

로그인 같은 동작은 보통 POST가 적절하다.
GET으로 로그인 처리하면 쿼리스트링 / 로그 / 히스토리에 남을 위험이 커질 수 있다.

 

정리: “매핑만 바꾸면 된다”는 건 서버 입장이고 “실제 요청 방식”은 클라이언트 (form / fetch)가 결정한다.


 

4) mid를 memberId로 쓰고 싶으면 어디를 수정해야 하나

 

핵심 개념: Command 객체 (커맨드 객체) / 데이터 바인딩

 

Spring MVC는 요청 파라미터를 커맨드 객체(DTO / VO 형태)에 자동 바인딩한다.
예: 폼이 name="mid"면 Spring은 setMid()를 찾아 호출한다.

mid → memberId로 바꾸려면 (정확한 수정 포인트)

DTO만 바꾸는 게 아니라 바인딩 이름이 맞는 전체 구간을 함께 맞춰야 한다.

 

 

(1) JSP / 클라이언트 파라미터 이름

  • name="mid" → name="memberId"

 

(2) MemberDTO 필드 / Setter 이름

  • private String mid; → private String memberId;
  • setMid() → setMemberId()

 

(3) DAO / SQL 매핑 (매우 중요)

 

DB 컬럼이 MID 그대로여도 상관없다.
다만 JDBC에서는 ResultSet 매핑을 수동으로 하므로:

  • rs.getString("MID") → dto.setMemberId(...) 처럼 DTO setter에 맞게 매핑해야 한다.

 

“테이블과 DTO가 달라도 되나?”

 

가능하다. 다만 실무에서는 보통

  • DB 컬럼명 (스네이크) ↔ 자바 필드명 (카멜)
    매핑을 명확히 관리한다.
    JDBC면 DAO에서 수동 매핑으로 해결하고 JPA면 @Column(name="MID") 같은 매핑을 쓴다.

정리: 커맨드 객체 바인딩 때문에 핵심은 파라미터명 ↔ DTO 필드 / Setter 일치이며 DB 컬럼명과의 연결은 DAO (또는 매핑 설정)이 담당한다.


 

5) Controller에 Service를 멤버변수로 넣는 이유 + @Autowired / @Service 의미

 

결론

 

Controller는 new로 Service를 직접 만들지 않고 스프링 컨테이너가 만든 Bean을 주입받아 사용한다.

이것이 DI (의존성 주입)다.

 

왜 new를 안 쓰나

  • 결합도 감소 (구현 교체 / 테스트 용이)
  • 생명주기 관리 (기본 singleton 관리)
  • AOP / 트랜잭션 같은 공통 관심사 확장에 유리

 

그래서 필요한 두 가지

  • 주입 받는 쪽: @Autowired
  • 주입 대상이 Bean이어야 함:
    • Service 구현체: @Service
    • DAO: @Repository
    • 기타 컴포넌트: @Component

 

“서비스가 하나면 이름 안 적어도 된다”

 

맞다. 타입 기준으로 주입할 Bean이 1개면 자동 주입된다.
단, 같은 타입 구현체가 2개 이상이면

  • @Qualifier("beanName") 또는
  • @Primary
    같은 선택 기준이 필요하다.

정리: DI는 @Autowired만 붙인다고 끝이 아니라 주입 대상도 Bean으로 등록되어 있어야 한다 (@Service 등).


 

6) session.setAttribute(...) vs model.addAttribute(...) 차이

 

결론

  • Model: “이번 요청에서 View (JSP)로 전달하는 임시 데이터 상자 (요청 단위)”
  • Session: “여러 요청에 걸쳐 유지되는 사용자 상태 저장소”

 

생명주기

  • Model: 요청 1회 처리 동안만 유효 (request scope)
  • Session: 서버에 저장되며 브라우저는 보통 세션ID 쿠키 (JSESSIONID 등)로 식별된다.
    • 페이지 이동 / 요청이 바뀌어도 유지되지만 서버 타임아웃 / 세션 만료 정책에 따라 종료된다.

 

무엇을 어디에 담나

  • Session에 적합: 로그인 사용자 정보, 권한, 장바구니, 인증 상태 같은 사용자 상태
  • Model에 적합: 게시글 목록 (datas), 검색 결과, 페이지네이션 결과 같은 화면 출력용 데이터

실무 포인트: 게시글 목록 같은 화면 데이터를 Session에 넣으면 다른 요청 / 다른 화면에서도 남아 데이터 오염이 생기거나 메모리 낭비가 발생할 수 있다.


 

7) "index"와 "redirect:index"의 차이

 

결론

  • return "index"
    • ViewResolver를 통해 index.jsp를 바로 렌더링 (요청 1번으로 종료)
  • return "redirect:index"
    • 서버가 리다이렉트 응답을 주고 브라우저가 새 요청을 한 번 더 보냄 (요청 2번)

 

“리다이렉트의 디폴트는 GET” 

 

대부분의 경우 (일반적인 302 / 303 동작) 브라우저는 리다이렉트 후 GET으로 재요청한다.
다만 307 / 308처럼 메서드 유지 리다이렉트도 존재하므로 정확히는 대부분 GET 재요청이 발생한다가 안전한 표현이다.

실무 팁 (경로 표현)

실무에서는 상대 경로 redirect:index보다 절대 경로 redirect:/index가 더 안전하다 (경로 꼬임 방지).

 

정리: redirect는 “새 요청을 만든다” → 그래서 로그가 2번 찍히는 것도 정상 패턴이다.


 

8) new vs @Component / @Controller / @Service / @Repository

 

결론

  • new는 개발자가 객체 생성 / 생명주기를 직접 관리한다.
  • Spring은 @Component 계열을 붙인 클래스를 스캔해 Bean으로 등록하고 컨테이너가 생명주기를 관리한다.

 

계층별로 주로 쓰는 어노테이션

  • Controller: @Controller
  • Service: @Service
  • DAO: @Repository
  • 기타 공용 컴포넌트: @Component

 

요약: new 대신 스프링 컨테이너 등록 (@Component 계열)주입 (@Autowired) 이 스프링 방식이다.


 

9) 요청 흐름 한 줄 요약 (로그인 예시)

  • /login 요청 발생
  • DispatcherServlet(FrontController)이 요청 수신
  • HandlerMapping이 @PostMapping("/login") 메서드 탐색
  • Controller 메서드 실행
  • Service → DAO → DB
  • Controller가 Model에 데이터 담음
  • return "main"이면 ViewResolver가 /WEB-INF/views/main.jsp로 연결
  • JSP 렌더링 결과가 브라우저로 반환

핵심: Spring MVC는 “요청을 Controller로 연결하고 View까지 렌더링하는 표준 파이프라인”을 제공한다.


 

10) “어노테이션은 싱글톤을 유지한다”의 정확한 의미

 

결론

 

@Controller, @Service, @Repository, @Component로 등록된 스프링 Bean의 기본 스코프가 singleton이다.

“어노테이션 자체가 싱글톤”이 아니라 컨테이너에 등록된 Bean의 기본 생명주기 정책이 singleton이라는 뜻이다.
필요하면 @Scope("prototype") 등으로 변경 가능하다.

 

정리: 기본값이 singleton이라 DI 구조가 가볍고 재사용이 가능해진다.


 

11) (발표 / 답변 스킬) 질문을 크게 말하기, 짧게 끊어 답변하기

  • 질문 요지를 한 문장으로 먼저 말하기
  • 답변은 짧은 덩어리로 끊어서 여러 번 말하기
  • 글 (지원서 / 자소서)도 제한 글자 수일수록 짧고 명확한 문장 여러 개로 구성하기
  • 문장을 의식적으로 “완결” 짓는 연습을 하면 전달력이 올라간다

 

 

  • WEB-INF: JSP 직접 접근 차단 → MVC 흐름 강제
  • pom.xml: DI가 아니라 dependency (JAR) 관리 → JSTL 동작 필수
  • GET /  POST: 매핑만이 아니라 클라이언트 요청 방식도 함께 맞춰야 함
  • 커맨드 객체: 파라미터명 ↔ DTO Setter 일치가 자동 바인딩 핵심
  • DI: @Autowired + 주입 대상 Bean 등록 (@Service / @Repository)
  • Session vs Model: 사용자 상태 (세션) vs 화면 전달 (모델)
  • redirect: 새 요청 발생 (대부분 GET), view name은 렌더링
  • Bean 스코프: 기본 singleton (“어노테이션 자체”가 아니라 “빈 정책”)