[내일배움캠프 Spring 4기] 48일차 TIL - Servlet | JDBC

서예진·2024년 2월 17일
1

오늘의 학습 키워드💻

▸ 코드카타
▸ Servlet
▸ JDBC


▼ 코트카타

2024년 2월 16일 - [프로그래머스] 24 : 예상 대진표 | N개의 최소공배수


▼ Servlet

  • Servlet(서블릿)은 자바를 사용하여 웹 페이지를 동적으로 생성하는 서버 측 프로그램 혹은 그 사양을 말함

사용자가 (HTTP) API 요청했을 때 서버의 서블릿 동작 과정


1. 사용자가 Client(브라우저)를 통해 서버에 HTTP Request 즉, API 요청을 함
2. 요청을 받은 Servlet 컨테이너는 HTTPServletRequest, HTTPServletResponse 객체를 생성함
- 약속된 HTTP의 규격을 맞추면서 쉽게 HTTP에 담긴 데이터를 사용하기 위한 객체
3. 설정된 정보를 통해 어떠한 Servlet에 대한 요청인지 찾는다.
4. 해당 Servlet에서 service 메서드를 호출한 뒤 브라우저의 요청 Method에 따라 doGet 혹은 doPost 등의 메서드 호출
5. 호출한 메서드들의 결과를 그대로 반환하거나 동적 페이지를 생성한 뒤 HttpServletResponse 객체에 응답을 담아 Client(브라우저)에 반환
6. 응답이 완료되면 생성한 HttpServletRequest, HttpServletResponse 객체를 소멸

Front Controller

  • 모든 API 요청을 앞서 살펴본 서블릿의 동작 방식에 맞춰 코드를 구현한다면 무수히 많은 Servlet 클래스를 구현해야함
  • 따라서 Spring은 DispatcherServlet을 사용하여 Front Controller 패턴 방식으로 API 요청을 효율적으로 처리하고 있음

Front Controller 패턴의 동작과정


Requser -> DispatherServlet -> Handler mapping -> Controller -> Model and logical view name -> View Resolver -> View -> Response

1. Client(브라우저)에서 HTTP 요청이 들어오면 DispatcherServlet 객체가 요청을 분석합니다.

2. DispatcherServlet 객체는 분석한 데이터를 토대로 Handler mapping을 통해 Controller를 찾아 요청을 전달해 줍니다.

  • GET /api/hello → HelloController 의 hello() 함수
  • GET /user/login → UserController 의 login() 함수
  • GET /user/signup → UserController 의 signup() 함수
  • POST /user/signup → UserController 의 registerUser() 함수

  • Handler mapping 에는 API path 와 Controller 메서드가 매칭되어 있습니다.
    		```
    		@RestController 
    		public class HelloController { 
    		@GetMapping("/api/hello") 
    		public String hello() { return "Hello World!"; } }
    	
    		```
     
  • API path 즉, URL 을 Controller에 작성하는 방법은 @Controller 애너테이션이 달려있는 클래스를 생성한 뒤 @GetMapping 처럼 요청한 HTTP Method 와 일치하는 애너테이션을 추가한 메서드를 구현합니다.
    - URL은 @GetMapping("/api/hello") 이처럼 해당 애너테이션의 속성값으로 전달해주면 됩니다.
    - 해당 메서드명은 URL을 매핑하는데 영향을 미치지 않음으로 자유롭게 정해도 상관 없습니다.
  • 이제는 직접 Servlet을 구현하지 않아도 DispatcherServlet에 의해 간편하게 HTTP 요청을 처리할 수 있게 되었습니다.

3. Controller -> DispatherServlet

  • 해당 Controller는 요청에 대한 처리를 완료 후 처리에 대한 결과 즉, 데이터('Modle')와 'View'정보를 전달

4. DispatcherServlet -> Client

  • ViewResolver 통해 View에 Model을 적용하여 View를 Client에게 응답으로 전달

▼ JDBC

JDBC란 무엇일까?

  • 애플리케이션 서버와 데이터베이스는 어떻게 소통하는지
    - 애플리케이션 서버에서 요청을 받고, 해당 요청을 처리하기 위해 데이터베이스와 소통해야함
  • 커넥션 연결, statement 준비 및 실행, 커넥션 종료 등의 반복적이고 중복되는 작업들을 대신 처리해주는 JdbcTemplate이 등장

JdbcTemplate 사용방법 - Controller

1. application.properties에 DB에 접근하기 위한 정보 작성

spring.datasource.url=jdbc:mysql://localhost:3306/{DB명}
spring.datasource.username=root
spring.datasource.password={비밀번호}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

2. build.gradle에 JDBC 라이브러리와 MySQL 등록

// MySQL
implementation 'mysql:mysql-connector-java:8.0.28'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'

3. Intellij Database 연동

4. DB연결이 필요한 곳에서 JdbcTemplate을 주입받아와 사용

private final JdbcTemplate jdbctemplate;

public MemoRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
}

5. 각 메서드에 맞게 코드 작성

package com.sparta.memo.controller;

import com.sparta.memo.dto.MemoRequestDto;
import com.sparta.memo.dto.MemoResponseDto;
import com.sparta.memo.entity.Memo;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.web.bind.annotation.*;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

@RestController
@RequestMapping("/api")
public class MemoController {

    private final JdbcTemplate jdbcTemplate;

    public MemoController(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @PostMapping("/memos")
    public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
        // RequestDto -> Entity
        Memo memo = new Memo(requestDto);

        // DB 저장
        KeyHolder keyHolder = new GeneratedKeyHolder(); // 기본 키를 반환받기 위한 객체

        String sql = "INSERT INTO memo (username, contents) VALUES (?, ?)";
        jdbcTemplate.update( con -> {
                    PreparedStatement preparedStatement = con.prepareStatement(sql,
                            Statement.RETURN_GENERATED_KEYS);

                    preparedStatement.setString(1, memo.getUsername());
                    preparedStatement.setString(2, memo.getContents());
                    return preparedStatement;
                },
                keyHolder);

        // DB Insert 후 받아온 기본키 확인
        Long id = keyHolder.getKey().longValue();
        memo.setId(id);

        // Entity -> ResponseDto
        MemoResponseDto memoResponseDto = new MemoResponseDto(memo);

        return memoResponseDto;
    }

    @GetMapping("/memos")
    public List<MemoResponseDto> getMemos() {
        // DB 조회
        String sql = "SELECT * FROM memo";

        return jdbcTemplate.query(sql, new RowMapper<MemoResponseDto>() {
            @Override
            public MemoResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
                // SQL 의 결과로 받아온 Memo 데이터들을 MemoResponseDto 타입으로 변환해줄 메서드
                Long id = rs.getLong("id");
                String username = rs.getString("username");
                String contents = rs.getString("contents");
                return new MemoResponseDto(id, username, contents);
            }
        });
    }

    @PutMapping("/memos/{id}")
    public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
        // 해당 메모가 DB에 존재하는지 확인
        Memo memo = findById(id);
        if(memo != null) {
            // memo 내용 수정
            String sql = "UPDATE memo SET username = ?, contents = ? WHERE id = ?";
            jdbcTemplate.update(sql, requestDto.getUsername(), requestDto.getContents(), id);

            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

    @DeleteMapping("/memos/{id}")
    public Long deleteMemo(@PathVariable Long id) {
        // 해당 메모가 DB에 존재하는지 확인
        Memo memo = findById(id);
        if(memo != null) {
						// memo 삭제
            String sql = "DELETE FROM memo WHERE id = ?";
            jdbcTemplate.update(sql, id);

            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

    private Memo findById(Long id) {
        // DB 조회
        String sql = "SELECT * FROM memo WHERE id = ?";

        return jdbcTemplate.query(sql, resultSet -> {
            if(resultSet.next()) {
                Memo memo = new Memo();
                memo.setUsername(resultSet.getString("username"));
                memo.setContents(resultSet.getString("contents"));
                return memo;
            } else {
                return null;
            }
        }, id);
    }
}
profile
안녕하세요

0개의 댓글