Dispatcher Servlet

: Http protocol로 들어오는 모든 요청을 가장 먼저 받아 적합한 Controller에 위임해주는 Front Controller(또는 Central Controller)이다.

 

특징

- HttpServlet을 상속한다.

     - DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet 상속구조

- 정적자원, 동적 자원을 분할 처리한다.

     - DispatcherServlet은 요청 처리할 컨트롤러를 우선적으로 찾고 컨트롤러가 없을 경우 정적 자원을 탐색한다.

     => 효율적 리소스 관리 가능

- 요청을 처리하기 위해 스페셜 빈(위임 컴포넌트)에 실제 작업을 위임한다.

 

스페셜 빈

: 스프링 MVC에서 제공하는 주요 빈으로 사용자가 재정의할 수 있다.

1. handlerMapping: 요청 처리할 컨트롤러 찾는 역할

     - @RequestMapping, @GetMapping 등...

2. handlerAdapter: 요청을 컨트롤러로 전달하고 반환값을 처리하는 역할

     - ResponseEntity, String, ModelAndView 등의 반환값에 따라 다르게 처리

3. handlerExceptionResolver: 요청에 대해 컨트롤러 실행 전, 중, 후에 예외 발생 시 이를 잡아내어 처리해주는 역할

     - @ExceptionHandler, @ControllerAdvice 등...

     - Dispatcher Servlet 내부에서 발생한 예외만 처리 가능(Filter, Interceptor, ServletContainer 수준에서의 예외는 처리 불)

4. viewResolver: 컨트롤러가 반환한 View 이름을 View 객체로 변환하는 역할

     - String으로 반환받은 경우 이름에 매핑된 view 객체로 변환

     - example.html, example.jsp 등...

5. LocaleResolver: Locale를 결정하는 역할

     - Locale: 유저의 언어, 지역, 출력 형식 등을 정의하는 문자열

     - message_en.properties, message_ko.properties 등...

     - url 파라미터 기반 로케일 변경하게 재정의 가능(?lang=ko)

 

처리과정

// Spring boot 구동 시 Dispatcher Servlet을 자동 등록, 모든 경로 '/'에 대해 매핑

1. Client Request: Client Request를 Dispatcher Servlet이 수신한다.

2. Handler Mapping: Dispatcher Servlet이 Handler Mapping 목록을 조회하여 요청 URI 에 해당하는 Controller를 찾는다.

3. Handler Adapter: 찾아낸 Controller를 적절한 Handler Adapter를 통해 호출한다.

4. Controller: Controller가 Service를 호출하고 Service 계층에서 비즈니스 로직을 처리한다.

5. Controller가 Response Entity를 반환한다.

6. Handler Adapter가 반환값을 클라이언트가 받을 수 있는 형태로 변환(JSON 등...) 후 Dispatcher Servlet으로 전달한다.

7. Dispatcher Servlet Client에게 응답을 반환한다.

목표

: JpaRepository를 상속받은 인터페이스를 이용해 데이터를 조회하는 방법에는 여러가지가 있는데 그 중에 findById()와 getReferenceById()를 이용해 원하는 데이터를 조회할 수 있다. 그렇다면 다른 함수로 존재하는 만큼 둘의 용도에 차이가 있을 것이라고 생각한다. 따라서 둘을 비교해보고 차이를 파악하여 적절한 메서드를 사용하고자 한다. 

 

1. findById()

: 조회할 엔티티의 식별자 id를 통해서 값을 반환하는 메서드이다.

- 리턴 타입: Optional<T>

     - 조회된 엔티티를 Optional로 감싸서 반환한다.

     - 영속성 컨텍스트에 없어도 예외를 던지는 것을 방지할 수 있다.

- 코드를 읽으면 바로 findById()가 실행된다

<코드>

Optional<Author> optionalAuthor = authorRepository.findById(authorId);
        System.out.println("=====================");
        System.out.println(optionalAuthor);

 

<결과>

Hibernate: 
    select
        a1_0.id,
        a1_0.authorName,
        a1_0.email,
        a1_0.password,
        a1_0.postDate,
        a1_0.updateDate 
    from
        author a1_0 
    where
        a1_0.id=?
=====================
Optional[com.example.taskmanagerapp.entity.Author@515b1401]

 

 

2. getReferenceById()

: 조회할 엔티티를 식별자 id를 통해서 값을 반환하는 메서드이다.

- 리턴 타입: T

     - 엔티티의 클래스 타입이다.

     - 영속성 컨텍스트에 없으면 EntityNotFoundException이 발생한다.

- 지연로딩 방식으로 엔티티 사용 전까지 조회를 지연해 프록시 객체로 갖고 있다가 해당 엔티티 객체가 필요한 시점에(출력, 사) 실제 엔티티를 반환한다. 

<코드>

        Author author = authorRepository.getReferenceById(authorId);
        System.out.println("=====================");
        System.out.println(author);

 

<결과>

=====================
Hibernate: 
    select
        a1_0.id,
        a1_0.authorName,
        a1_0.email,
        a1_0.password,
        a1_0.postDate,
        a1_0.updateDate 
    from
        author a1_0 
    where
        a1_0.id=?
com.example.taskmanagerapp.entity.Author@2f1be231

 

- 해당 객체의 id값을 호출할 때는 SQL이 실행되지 않는다.

     - getReferenceById()는 프록시 객체를 반환하는데 이는 내부적으로 id값만 세팅되어 있고 나머지는 초기화되어 있지 않다.

          - 실제 필드 접근 할 때에 프록시 객체가 실체화된다.

          - 프록시: id만 세팅되어 있는 껍데기 객체

 

 

정리

  findById() getReferenceById()
반환 타입 Optional<T> T
동작 방식 Eager Load(즉시 조회) Lazy Load(지연 조회)
쿼리 실행 시점 메서드 호출 시 실제 필드 접근 시점
프록시 사용 X O
예외 발생 여부 X EntityNotFoundException 발생(Entity 없을 시)
용도 데이터 즉시 사용 필요한 경우 엔티티 참조만 필요한 경우

 

문제 상황

: 데이터베이스와 requestDto의 필드명을 Camel Case로 통일했었는데 로직 상 오류가 없는데도 계속해서 에러가 발생했다.

콘솔의 에러 문구를 확인해보니 다음과 같았다. 

Hibernate: 
    /* insert for
        com.example.taskmanagerapp.entity.Author */insert 
    into
        author (author_name, email, password, post_date, update_date) 
    values
        (?, ?, ?, ?, ?)
2025-05-15T21:07:04.410+09:00  WARN 14036 --- [taskManagerApp] [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1364, SQLState: HY000
2025-05-15T21:07:04.410+09:00 ERROR 14036 --- [taskManagerApp] [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : Field 'authorName' doesn't have a default value
2025-05-15T21:07:04.434+09:00 ERROR 14036 --- [taskManagerApp] [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.orm.jpa.JpaSystemException: could not execute statement [Field 'authorName' doesn't have a default value] [/* insert for com.example.taskmanagerapp.entity.Author */insert into author (author_name,email,password,post_date,update_date) values (?,?,?,?,?)]] with root cause

java.sql.SQLException: Field 'authorName' doesn't have a default value

 

원인

: 위의 에러코드를 확인해보면 insert 문에서 필드명이 authorName이 아니라 author_name으로 변환되어 있는 것을 확인할 수 있다. JPA는 기본적으로 필드명을 Snake Case로 자동 변환해서 테이블 컬럼에 매핑한다. 따라서 authorName이 author_Name으로 변환되어 DB에서 해당 컬럼을 찾고 있었던 것이다. 그러나 DB에는 Camel Case로 작성된 컬럼 authorName만 있기 때문에 에러가 발생했다. 

 

해결

 : application.properties에 Hibernate의 PhysicalNamingStrategy와 ImplicitNamingStrategy를 설정해 Snake Case로 변환하지 못하도록 할 수 있다. 

spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl

 

추가 문제 상황 발생

: 위의 해결책을 통해 JPA가 자동으로 Snack Case로 변환하는 것은 방지했으나 에러가 계속해서 발생했다. 에러 코드 상에서

필드명은 Camel Case로 정상적인 상태였으나 그 외의 에러 메세지는 그대로였다. 

 

원인

: DB에서 author 테이블을 확인해보니 author_name 컬럼이 추가되어 있었다. Hibernate는 ddl-auto=update 또는 create 상태일 때 기존 테이블과 엔티티가 일치하지 않으면 컬럼을 만들어버린다. 따라서 author_name이 자동으로 생성된 것이다.

 

해결책

: 새로 생성된 Snake Case의 컬럼을 없애주면 정상적으로 작동한다. 

문제상황

: 애플리케이션을 실행할 때마다 기존에 데이터베이스에 저장해둔 데이터가 사라진다.

 

원인

: application.properties 파일의 JPA DDL 자동 설정에 이유가 있다.

spring.jpa.hibernate.ddl-auto=create

위의 코드가 작성되어 있었다. 이는 애플리케이션을 실행하면 기존 테이블을 전부 drop 하고 엔티티 정의에 따라 새로 테이블을 생성하도록 정의한 것이다. 따라서 매 실행마다 데이터가 삭제된 것이다. 

 

해결책

: 설정값을 바꾼다.

spring.jpa.hibernate.ddl-auto=update

update로 설정값을 변경하면 엔티티 변경사항만 반영되고 데이터는 보존된다. 즉 앞서 발생했던 데이터가 실행마다 사라지는 문제를 해결할 수 있다.

 

여러가지 설정값

: 그 외에도 다양한 설정값이 있으니 참고하자.

설정 값 설명
create 애플리케이션을 실행할 때마다 새로 테이블을 생성한다. 기존 데이터는 모두 삭제된다.
create-drop 애플리케이션 종료 시 테이블이 삭제된다. 테스트 할 때 유용하다.
update 엔티티 변경사항만 반영되고 데이터는 보존된다.
validate 스키마가 엔티티와 맞는지 검증만 하고 수정은 하지 않는다.
none 테이블을 생성하거나 수정하지 않는다. 즉 db 스키마를 건드리지 않는다. 

 

+ Recent posts