섹션 2. Database 조작하기
목표
디스크와 메모리의 차이를 이해하고, Database의 필요성을 이해
MySQL Database를 SQL과 함께 조작
스프링 서버를 이용해 Database에 접근하고 데이터를 이용
API의 예외 상황을 알아보고 예외를 처리
문제점
저번에 한 작업을 다시 하려고 서버를 켜니 기존에 저장했던 데이터가 모두 사라졌다! 왜 이런 일이 생겼을까?
서버는 당연하게도 이런 일이 생기면 안된다.
이러한 일을 알기 위해 컴퓨터의 부품들을 살펴보자.
CPU -> 연산, 사람의 뇌!
RAM -> 단기 기억장치
DISK -> 장기 깅억장치
개발하고 있는 서버는 DISK에 있다. 서버를 실행시키면 DISK의 코드 정보가 RAM으로 복사된다. API가 실행되면 연산이 수행되며 CPU와 RAM을 왔다갔다 한다. 이 유저 정보는 메모리인 RAM에 저장된다. 프로그램이 종료되면 RAM에 있는 모든 정보가 사라진다. 그렇게 때문에 유저 정보가 사라진 것이었다.
서버에서는 어떻게 DISK에 저장할 수 있을까?
이럴 때 바로 DataBase를 사용한다!
데이터베이스란 데이터를 구조화 시켜 저장하는 일을 한다.
관계형 데이터베이스인 MySQL을 이용해서 서버의 효율을 늘려보자.
먼저 스프링이 데이터베이스에 접근할 수 있게 yml 파일을 설정해준다.
spring:
datasource:
url: "jdbc:mysql://localhost/library"
username: "root"
password: "~~~~~~"
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc를 이용해서 데이터베이스에 접근할 수 있게 해준다.
create table user
(
id bigint auto_increment,
name varchar(25),
age int,
stocked_date date,
primary key (id)
);
이제 UserController
로 가서 POST API를 변경해보자.
@RestController
public class UserController {
private final JdbcTemplate jdbcTemplate;
public UserController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@PostMapping("/user")
public void saveUser(@RequestBody UserCreateRequest request) {
String sql = "INSERT INTO user(name, age) VALUES(?, ?)";
jdbcTemplate.update(sql, request.getName(), request.getAge());
}
}
이렇게 final 변수를 통해 생성자를 만들면 스프링이 알아서 jdbcTemplate를 넣어준다
값이 들어갈 부분은 각 데이터마다 다르기 때문에 ?
로 처리해 줬고 아래의 update 문에서 name, age를 받아서 값으로 이용하도록 했다.
@GetMapping("/user")
public List<UserResponse> getUsers() {
String sql = "SELECT * FROM user";
return jdbcTemplate.query(sql, new RowMapper<UserResponse>() {
@Override
public UserResponse mapRow(ResultSet rs, int rowNum) throws SQLException {
long id = rs.getLong("id");
String name = rs.getString("name");
int age = rs.getInt("age");
return new UserResponse(id, name, age);
}
});
}
query를 사용해 select 쿼리를 날리고 resultset 객체에는 결과가 담겨있고 이 객체에 getLong, getString과 같은 메서드를 사용해서 실제 값을 가져올 수 있다.
3.도서관 사용자 이름을 업데이트 할 수 있다.
-> HTTP Method를 PUT로 설정하고 Path를 /user, Body를 JSON을 이용한다. JSON은 id와 name를 담는다.
4.도서관 사용자를 삭제 할 수 있다.
-> HTTP Method를 DELETE로 설정하고 Path를 /user, 쿼리를 사용해서 삭제되어야 하는 문자열 name을 사용한다.
@PutMapping("/user")
public void updateUser(@RequestBody UserUpdateRequest request) {
String sql = "UPDATE user SET name = ? WHERE id = ?";
jdbcTemplate.update(sql, request.getName(), request.getId());
}
@DeleteMapping("/user")
public void deleteUser(@RequestParam String name) {
String sql = "DELETE FROM user WHERE name = ?";
jdbcTemplate.update(sql, name);
}
public class UserUpdateRequest {
private long id;
private String name;
public long getId() {
return id;
}
public String getName() {
return name;
} }
이렇게 작성을 했고 잘 작동을 한다.
단, 한 가지 문제가 있다 만약 존재하지 않는 유저의 경우를 테스트해봐도 잘 처리되었다는 200 OK
를 반환한다. 이를 위해 에러 처리를 해주겠다.
@PutMapping("/user")
public void updateUser(@RequestBody UserUpdateRequest request) {
String readSql = "SELECT * FROM user Where id = ?";
boolean isUserNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.getId()).isEmpty();
if (isUserNotExist) {
throw new IllegalArgumentException();
}
String sql = "UPDATE user SET name = ? WHERE id = ?";
jdbcTemplate.update(sql, request.getName(), request.getId());
}
@DeleteMapping("/user")
public void deleteUser(@RequestParam String name) {
String readSql = "SELECT * FROM user Where name = ?";
boolean isUserNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, name).isEmpty();
if (isUserNotExist) {
throw new IllegalArgumentException();
}
String sql = "DELETE FROM user WHERE name = ?";
jdbcTemplate.update(sql, name);
}
}
에러를 잘 던져준다! 이렇게 우리는 유저에 관련된 기능을 다 만들었다.
하지만 지금 코드에는 문제가 있다. 한 클래스인 Controller에서 모든 기능을 유지하고 있고 이는 좋은 코드는 아니다. 다음 섹션에서 왜 모든 기능을 한 섹션에서 구현하면 안되는지 알아보고 스프링의 핵심 개념을 통해 이 문제를 해결해보자.
이번주 회고
: 어느 정도 알았다고 생각한 부분이라 가볍게 들었지만 부족한 점이 많아서 혼자 생각하는 시간이 많았다. 뒤의 JPA 부분이나 컨테이너 부분 쪽으로 가면 더 어려워지니 기초를 튼튼히 해야겠다는 생각을 했다. 데이터베이스 부분은 특히 친숙하지 않은 jdbc를 써서 sql을 바로 연결하는 부분이 재밌었다. 평소에는 해보지 않았던 방법이라 흥미가 있었고 더욱 좋은 툴로 사용할 수 있을 것 같다.
댓글을 작성해보세요.