다음은 글은 infrean의 "자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]" 강의의 학습 목적으로 작성된 것입니다. 강의의 디테일한 내용이나, 코드 등은 빠져있을 수 있습니다.
1. Database와 MySQL
1-1. RDB(Relational Database)
= 관계형 데이터베이스
이는 테이블 형식으로 데이터를 저장하고 관리하는 데이터베이스 시스템입니다.
- 데이터를 행(row)과 열(column)의 테이블로 표현.
- 테이블 간에는 관계(relationship)가 있을 수 있음.
- 각 테이블의 행은 유일한 식별자(primary key)를 가짐.
ex) MySQL
1-2. SQL(Structured Query Language)
SQL은 구조화된 질의 언어(Structured Query Language)로, 데이터베이스에서 데이터를 조작하고 관리하는 데 사용된다.
데이터베이스에서 데이터를 조회, 삽입, 수정, 삭제하는 명령을 수행한다.
1-3. MySQL에 접근하는 방법
IntelliJ Ultimate에서 사용 가능하다. (학생인증하면 무료니까 구글링해보자!)
혹은 터미널에서 사용해도 똑같이 사용할 수 있다.
2. MySQL에서 테이블 만들기
우리가 데이터를 테이블(표)에 넣어서 관리하는 것에 가장 익숙한 형태가 "엑셀"이다.
이를 엑셀에 비유해보자.
- 폴더 = 데이터베이스
- 엑셀 파일 = 테이블
- 엑셀 파일의 헤더 = 테이블의 필드 정의
- 엑셀 파일의 서식 = 테이블의 필드 타입
2-1 . 몇 가지 기본 명령문들
// 데이터베이스 만들기
create database [데이터베이스 이름];
// 데이터베이스 목록 보기
show database;
// 데이터베이스 제거 하기
drop database [데이터베이스 이름];
// 데이터베이스 안으로 들어가기
use [데이터베이스 이름];
// 테이블 목록 보기
show tables;
// 테이블 만들기
create table [테이블 이름](
[필드1 이름] [타입] [부가조건],
[필드2 이름] [타입] [부가조건],
...
primary key ([필드이름])
);
// 테이블 제거하기
drop table [테이블 이름];
2-2. 테이블 만들기의 예시
create table fruit
(
id bigint auto_increment,
name varchar(20),
price int,
stocked_data date,
primary key (id)
);
auto_increment : 데이터를 명시적으로 넣지 않더라도 1부터 1씩 증가하면 자동 기록된다.
primary key : 유일한 키 ; 위의 데이터 (name, price, stocked_data)가 완벽히 동일해도 id 를 통해 구별한다.
2-3. MySQL 타입 살펴보기
1) 정수 타입 (괄호는 JAVA의 타입)
- tinyint : 1 바이트 정수 (byte)
- int : 4 바이트 정수 (int)
- bigint : 8 바이트 정수 (long) ;id는 21억건이 넘을 수도 있으니, 보통 가장 큰 타입인 bigint로 설정한다.
2) 실수 타입
- double : 8 바이트 정수
- decimal(A,B) : 소수점을 B개 가지고 있는 전체 A자릿수 실수
ex ) decimal(2,4) : 12.23 과 같은 수를 표현 가능
3) 문자열 타입
- char(A) : A 글자수<고정>가 들어갈 수 있는 문자열
- varchar(A) : 최대 A 글자수가 들어갈 수 있는 문자열
4) 날짜 / 시간 타입
- date : 날짜, yyyy-MM-dd
- time : 시간, HH:mm:ss
- datetime : 날짜와 시간을 합친 타입, yyyy-MM-dd HH:mm:ss
제목2 에서 배운 명령어들을 "정의"와 관련된
DDL(Data Definition Language)이라고 한다.
3. 테이블의 데이터를 조작하기 (CRUD)
3-1. fruit 테이블에 과일을 추가하고, 조회하고, 수정하고, 삭제하기!
- 데이터를 넣는다 = 생성, Create
- 데이터를 조회한다 = 읽기, Retrieve or Read
- 데이터를 수정한다 = 업데이트, Update
- 데이터를 삭제한다 = 제거, Delete
=> CRUD
3-2. Data 넣기
- 형식
INSERT INTO [테이블 이름] (필드1이름, 필드2이름, ...)
VALUES (값1, 값2, ...)
- 예시
INSERT INTO fruit (name, price, stocked_date)
VALUES ('사과', 1000, '2023-01-01');
- INSERT INTO, VALUES 와 같은 문법에서 대문자/소문자를 구별하지 않아도 된다.
- 괄호 안의 필드와 값의 순서가 중요하다.
- id는 지정해주지 않아도, auto_increment 덕분에 자동으로 추가되며, +1 된다.
3-3. Data 조회하기
- 형식
SELECT * FROM [테이블 이름];
-예시
SELECT * FROM fruit;
- * 는 모든 데이터를 조회한다는 뜻.
- * 대신 필드 이름을 넣을 수도 있고, 여러개 넣을 수도 있다.
SELECT name, price from fruit;
- WHERE 명령어로 filter를 걸 수도 있다.
SELECT * FROM [테이블 이름] WHERE [조건];
SELECT * FROM fruit WHERE name = '사과' AND price <= 2000;
- 다음과 같이 AND, OR 등을 사용하여 조건을 여러 개 이어붙일 수 있다.
// 이외의 조건
!= // 아닌
<, >, >= // 부등호
BETWEEN // 사이의 값
[필드 이름] IN [Value]
//예시
SELECT * FROM fruit WHERE name IN ('사과','바나나'):
[필드 이름] NOT IN [Value]
//예시
SELECT * FROM fruit WHERE name NOT IN ('사과');
// 이름이 '사과'가 아닌 과일 조회
3-4. Data 업데이트하기
- 형식
UPDATE [테이블 이름] SET 필드1이름=값, 필드2이름=값, ... WHERE [조건];
- 예시
UPDATE fruit SET price=1500 WHERE name='사과';
-> 이름이 사과인 모든 과일은 1500원으로 price를 세팅. (변경)
🚨주의 :
만약 [조건]을 붙이지 않으면, 모든 데이터가 업데이트된다!!!
조건을 잘 적어주는 것이 중요!
3-5. Data 삭제하기
- 형식
DELETE FROM [테이블 이름] WHERE [조건];
- 예시
DELETE FROM fruit WHERE name='사과';
🚨주의 :
마찬가지로, 만약 [조건]을 붙이지 않으면, 모든 데이터가 삭제된다!!!
조건을 잘 적어주는 것이 중요!
제목3 에서 배운 명령어들을 "정의"와 관련된
DML(Data Manipulation Language)이라고 한다.
4. Spring에서 Database 사용하기
지금까지는 우리가 MySQL를 직접 접근했다.
우리의 스프링 서버가 MySQL DB에 접근하도록 해보자.
4-1. application.yml을 만들고 설정하기
해당 위치 (resources) 폴더 내부에 "application.yml"을 만든다.
- application.yml 파일은 주로 애플리케이션의 설정 정보를 기술하는 데 사용된다.
- 이 파일에는 데이터베이스 연결 정보, 서버 포트, 보안 설정, 프로파일 관리, 로깅 설정 등과 같은 다양한 설정이 포함될 수 있습니다.
//application.yml
spring:
datasource:
url: "jdbc:mysql://localhost/library"
username: "root"
password: "1234"
driver-class-name: com.mysql.cj.jdbc.Driver
1) jdbc:mysql:// - jdbc를 이용해 mysql에 접근한다
localhost – 접근하려는 mysql은 localhost에 있다.
/library – 접근하려는 DB는 library이다.
2) mysql의 username 과 password 입력
3) 데이터베이스에 접근할 때 사용할 프로그램
4-2. 유저 정보를 저장할, user 테이블 만들기
create table user (
id bigint auto_increment,
name varchar(25),
age int,
primary key (id)
);
4-3. POST API 변경하기
private final List<User> users = new ArrayList<>();
기존에 list에 저장하고 있었던 코드를 지워주고, 다음과 같이 바꿔준다.
private final JdbcTemplate jdbcTemplate;
public UserController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
코드 생략.
최근에는 JdbcTemplate 대신 Spring Data JPA와 같은 ORM(Object-Relational Mapping) 기술을 사용하는 경향이 늘어나고 있지만, 기본적으로 알아둬야할 템플릿이다.
4-4. GET API 변경하기
@GetMapping("/user") //GET /user
public List<UserResponse> getUser() {
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);
}
});
}
...해당 부분이 GET API 부분이다.
jdbcTemplate.query(sql, RowMapper 구현 익명클래스) 형태로 되어 있다.
익명 클래스는 이름이 없는 클래스로, 클래스를 정의하면서 동시에 객체를 생성하는 방법이다.
일회성으로 사용되는 경우나 간단한 인터페이스의 구현체를 생성할 때 주로 활용된다.주로 인터페이스나 추상 클래스의 구현체를 정의하고 객체를 생성하는 데 사용된다.
사용 예시: new SomeInterface() { /* 구현 내용 */ };
jdbcTemplate.query() 메서드는 SQL 쿼리를 실행하고, 그 결과를 RowMapper를 통해 매핑하여 리스트로 반환한다.
여기서는 RowMapper<UserResponse>를 사용하여 각 행의 결과를 UserResponse 객체로 매핑합니다.
mapRow 메서드에서는 ResultSet 객체에서 각 열의 값을 읽어와서 UserResponse 객체를 생성하고 반환한다.
=> Java 람다식으로 간단히 변경 가능(Syntax Sugar)
(Option+Enter)
5. 유저 업데이트 API, 삭제 API 개발과 테스트
비슷한 방식으로 처리해보자.
6. 유저 업데이트 API, 삭제 API 예외 처리 하기
🤔 존재하지 않는 유저라도 성공했다고 200 OK 를 전달한다...
즉, 존재하지 않는 유저를 업데이트하거나 삭제를 해도, 별다른 에러메시지나 에러 HTTP STATUS가 오도록 처리하지 않았다.
java 문법 중 throw 를 통해 exception을 던지는 방법이 기억난다.
(throw 키워드는 예외를 명시적으로 던질 때 사용됩다. 예외를 던지면 예외가 발생한 지점에서 해당 예외를 처리하는 부분으로 제어가 전달된다.)
API에서 예외를 던진다면?
@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();
}
// .. 이하동일
그냥 사용자가 존재하는지 확인하는 쿼리를 수행한다고 보면 된다.
1) id를 기준으로 유저가 존재하는지 SELECT 쿼리 작성
2) ? 자리에는 request.getId() 가 들어간다.
3) SQL을 날려 DB에 데이터가 있으면 0 반환!
4) query는 반환된 값을 list로 감싸준다.
결론적으로 request에서 Id를 뽑는다!!
-> 그리고, 해당 id를 가진 유저가 있는지 확인
-> 있다면 0으로 변환
-> [0]이 return!!!
-> 없다면 [ ] 빈 list가 return!!!
=> list가 비어있다면, .isEmpty() 를 통해 확인하고 true를 반환.
=> 즉, isUserNotExist = true가 된다. means " 존재하지 않는다 "
∴ exception을 throw 해준다.
// 삭제도 같은 방식으로 해보자.
🚨 한 클래스인 Controller에서 너무 많은 역할을 하고 있다!!!!!! 는 문제가 있지만... 이는 다음 section에서 해결해보자.