초록 스터디 3기 2주차 - JDBC를 이용한 방탈출 API 작성

코코코딩을 합시다·2024년 3월 28일
post-thumbnail

업무를 함에 있어 가장 효율성을 극대화 시킬 수 있는 방법이 뭔줄 아는가?
명확한 목표가 있어야 한다 는 점이다.
이번 실습에 있어 나의 목표는 JDBC를 완전히 깨우친다거나, 유의미한 이슈를 발견한다던가,
는 아니고. 미션 제출일 9시에 월드컵 태국전이 있어서 놀라운 집중력으로 1시간만에 제출을 할 수 있었다. 개발이 하기 싫을 땐 이거 끝내고 집 가서 뭐할지 정해놓고 해보셈. 효과 개굿 ㄷㄷ

1. 데이터베이스 적용하기

1.1 build.gradle 파일을 이용하여 다음 두 의존성을 추가하세요.

  • spring-boot-stater-jdbc
  • h2
--build.gradle

dependencies {
...    
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
...
}

1.2 데이터베이스 테이블 생성을 위해 schema.sql 파일을 생성하고 테이블을 생성하는 쿼리를 작성하세요.

--schema.sql

CREATE TABLE reservation
(
    id      BIGINT       NOT NULL AUTO_INCREMENT,
    name    VARCHAR(255) NOT NULL,
    date    VARCHAR(255) NOT NULL,
    time    VARCHAR(255) NOT NULL,
    PRIMARY KEY (id)
);

1.3

  • h2 데이터베이스의 console 기능을 활성화하세요.
  • datasource url을 다음과 같이 지정하세요.
    • jdbc:h2:mem:database
--application.properties

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.url=jdbc:h2:mem:database

테스트 코드

@Autowired
private JdbcTemplate jdbcTemplate;

@Test
void 오단계() {
    try (Connection connection = jdbcTemplate.getDataSource().getConnection()) {
        assertThat(connection).isNotNull();
        assertThat(connection.getCatalog()).isEqualTo("DATABASE");
        assertThat(connection.getMetaData().getTables(null, null, "RESERVATION", null).next()).isTrue();
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}


2. 데이터 조회하기

  • 예약 조회 API 처리 로직에서 저장된 예약을 조회할 때 데이터베이스를 활용하도록 수정하세요.

👉 데이터베이스가 도입됐으니 DAO(Repository)를 도입해야겠다!
👉 DAO의 도입에 따라 Service 코드에도 적절한 수정이 필요하겠다.

이전 실습에서는 List 객체에 임시로 데이터를 담아 List를 조회했지만 이번엔 데이터베이스에 데이터를 담고 JDBC 메소드를 이용해 데이터를 읽어올 것이다.

--QueryDAO.java

@Repository
public class QueryDAO {

    // JdbcTemplate 을 사용하여 SQL 문을 실행할 수 있다.
    // JdbcTemplate 은 PreparedStatement 를 생성하고 SQL 매개변수를 설정하며, ResultSet 을 처리할 수 있다.
    private JdbcTemplate jdbcTemplate;

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

    public List<Reservation> findAllReservations() {
        String sql = "SELECT id, name, date, time FROM reservation";
        return jdbcTemplate.query(
                sql,
                (resultSet, rowNum) -> {
                    Reservation reservation = new Reservation(
                            resultSet.getLong("id"),
                            resultSet.getString("name"),
                            resultSet.getString("date"),
                            resultSet.getString("time")
                    );
                    return reservation;
                });
    }


}
  • Spring에서는 JdbcTemplate이라는 객체를 제공하여 데이터베이스 접근을 돕는다.
  • 지난 게시물에서 언급했듯 query 메소드는 여러 객체를 조회하는 메소드이다.
  • 두번째 매개변수는 매핑 타입으로, 여기서는 Reservation 클래스를 사용했다.
  • 두번째 매개변수에는 RowMapper을 넣어 데이터를 조회할 수도 있다.
--ReservationService.java

public class ReservationService {

    @Autowired
    private QueryDAO queryDAO; // DAO 주입

    public List<Reservation> getAllReservations() {
        return queryDAO.findAllReservations();
    }
    
}
  • DAO로 읽어온 모든 예약 데이터를 Reservation 타입의 List에 넣어 반환한다.

테스트 코드


@Test
void 육단계() {
    jdbcTemplate.update("INSERT INTO reservation (name, date, time) 
    VALUES (?, ?, ?)", "브라운", "2023-08-05", "15:40");

    List<Reservation> reservations = RestAssured.given().log().all()
            .when().get("/reservations")
            .then().log().all()
            .statusCode(200).extract()
            .jsonPath().getList(".", Reservation.class);

    Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class);

    assertThat(reservations.size()).isEqualTo(count);
}

3. 데이터 추가/삭제하기

  • 예약 추가/취소 API 처리 로직에서 데이터베이스를 활용하도록 수정하세요.
  • 예약 관리 기능이 정상 동작하도록 기능을 완성하세요.

@Repository
public class QueryDAO {

    private JdbcTemplate jdbcTemplate;

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

    public void addReservation(Reservation reservation) {
        String sql = "INSERT INTO reservation(name, date, time) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, reservation.getName(), reservation.getDate(), reservation.getTime());
    }

    public void deleteReservation(Long id){
        String sql = "DELETE FROM reservation WHERE id = ?";
        jdbcTemplate.update(sql, id);
    }
    ...

}

👉 앞서 조회에는 query 메소드를 사용한 반면 추가 및 삭제에선 update 메소드를 사용한다.
👉 Native SQL을 사용하는 JDBC에서 SQL문 작성 역량은 중요하다.

--ReservationController.java

@Controller
public class ReservationController {
	...
    final AtomicLong index = new AtomicLong(0);
    @PostMapping("/reservations")
    public ResponseEntity<Void> create(@RequestBody Reservation reservation) {
        reservationService.addReservation(reservation);
        return ResponseEntity.created
        (URI.create("/reservations/" + index.incrementAndGet())).build();
    }
    @DeleteMapping("/reservations/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        reservationService.deleteReservation(id);
        return ResponseEntity.noContent().build();
    }
    ...
}

테스트 코드


    @Test
    void 칠단계() {
        Map<String, String> params = new HashMap<>();
        params.put("name", "브라운");
        params.put("date", "2023-08-05");
        params.put("time", "10:00");

        RestAssured.given().log().all()
                .contentType(ContentType.JSON)
                .body(params)
                .when().post("/reservations")
                .then().log().all()
                .statusCode(201)
                .header("Location", "/reservations/1");

        Integer count = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class);
        assertThat(count).isEqualTo(1);

        RestAssured.given().log().all()
                .when().delete("/reservations/1")
                .then().log().all()
                .statusCode(204);

        Integer countAfterDelete = jdbcTemplate.queryForObject("SELECT count(1) from reservation", Integer.class);
        assertThat(countAfterDelete).isEqualTo(0);
    }

profile
좋아하는 걸로 밥 벌어먹기

0개의 댓글