RDBMS
는 줄여서 RDB는 관계형 데이터 베이스를 말함H2
는 RDBMS의 한 종류로 서버가 켜져있는 동안에만 작동하는 RDB스프링은 자바로 작동하고 데이터데이스는 SQL로 작동
이번에 사용할 RDBMS는 H2와 MySQL
1. src > main > resources > application.properties 파일을 열기
2. 해당 H2 웹콘솔 설정 넣기
# Spring에 h2라는 데이터베이스에 콘솔 보이게 해줘 = true
spring.h2.console.enabled=true
# Spring에 datasource, 데이터베이스를 뭐 쓸 거냐면 h2를 쓸거야
spring.datasource.url=jdbc:h2:mem:testdb
3. Week02Application.java 파일 Run
4. http://localhost:8080/h2-console 로 접속
이것은 뜻은 내 컴퓨터에서 8080번째 방에 들어가자 5. Connect 클릭
웹콘솔 띄우기 성공!
💡
데이터베이스
는 매우 고도화된 엑셀
- 엑셀 파일 하나가 "데이터베이스",
- 엑셀 시트 하나는 "테이블",
- 엑셀 행 하나는 "데이터"라고 부른다.
// courses라는 테이블이 존재를 안하면 만들어라
CREATE TABLE IF NOT EXISTS courses (
// bigint는 SQL에서 Long인 개념
// AUTO_INCREMENT 자동으로 1 2 3 4 이렇게 증가해라
id bigint NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
tutor varchar(255) NOT NULL,
// NOT NULL => 반드시 가지고 있어라
PRIMARY KEY (id)
// PRIMARY KEY => 유일한 값, ID로 구분할 것이다
);
정리하자면 데이터를 넣을 때 title이랑 tutor라는 데이터를 넣어 주고 id를 자동으로 증가시켜줘! 그리고 title이랑 tutor는 문자열이야~ 라는 의미
INSERT INTO courses (title, tutor) VALUES
('웹개발의 봄, Spring', '남병관'), ('웹개발 종합반', '이범규');
SELECT * FROM courses;
위에서 사용한 생성, 삽입, 조회 명령문을 SQL(Structured Query Language)이라고 한다.
JPA
란 SQL을 쓰지 않고 자바를 이용해서 데이터를 생성, 조회, 수정, 삭제할 수 있도록 해주는 번역기
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
자바코드를 번역해주어서 데이터베이스를 뭘로 바꾸느냐가 상관없을 뿐만 아니라 명령도 자바로 만들면 된다.
1. src > main > java > com.sparta.week02에 domain 이라는 패키지를 만듭니다.
2. Course.java, CourseRepository.java 파일을 만듭니다.
3. Course.java 클래스
@NoArgsConstructor // 기본생성자를 대신 생성해줍니다.
@Entity // 테이블임을 나타냅니다.
public class Course {
@Id // ID 값, Primary Key로 사용하겠다는 뜻입니다.
@GeneratedValue(strategy = GenerationType.AUTO) // 자동 증가 명령입니다.
private Long id;
@Column(nullable = false) // 컬럼 값이고 반드시 값이 존재해야 함을 나타냅니다.
private String title;
@Column(nullable = false)
private String tutor;
public String getTitle() {
return this.title;
}
public String getTutor() {
return this.tutor;
}
public Course(String title, String tutor) {
this.title = title;
this.tutor = tutor;
}
}
4. CourseRepository.java 인터페이스
import org.springframework.data.jpa.repository.JpaRepository;
public interface CourseRepository extends JpaRepository<Course, Long> {
// CourseRepository는 SQL의 역할을 대신해줌.
// Course에 관한 Repository이다.
}
💡 인터페이스(Interface)란?
클래스에서 멤버가 빠진, 메소드 모음집
JPA는 Repository를 통해서만 사용할 수 있음
1. SQL이 보이도록 application.properties 세팅
spring.jpa.show-sql=true
2. JPA 실행 코드 main 함수에 붙이기
public static void main(String[] args) {
SpringApplication.run(Week2Application.class, args);
}
// Week02Application.java 의 main 함수 아래에 붙여주세요.
@Bean
public CommandLineRunner demo(CourseRepository repository) {
return (args) -> {
Course course1 = new Course("웹개발의 봄 Spring", "강지영");
repository.save(course1);
List<Course> courseList = repository.findAll();
for(int i = 0; i <courseList.size(); i++){
Course c = courseList.get(i);
System.out.println(c.getTitle());
System.out.println(c.getTutor());
}
};
}
3. 웹콘솔에서 확인해보기
extends
상속할 클래스명{}1. Timestamped.java 클래스 생성
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
// 이 클래스를 상속하면 멤버 변수가 있을 텐데 그것을 컬럼으로 인식해줘
@MappedSuperclass // 상속했을 때, 컬럼으로 인식하게 합니다.
//Entity가 course같은 테이블, 이것을 주시하다가 뭔가 수정이 일어날 때 자동으로 반영해줘
@EntityListeners(AuditingEntityListener.class) // 생성/수정 시간을 자동으로 반영하도록 설정
public abstract class Timestamped {
// abstract 추상 => 이거 직접 구현 안됨 상속으로 구현된다 extens Timestamped
// @ => 어노테이션
// @의 역할은 스프링에게 야 이거 이런 역할이 있어라고 알려주는 것
// 생성일자
@CreatedDate // 생성일자임을 나타냅니다.
// [자료형] LoclDateTime = 시간을 나타내는 자바의 자료형
private LocalDateTime createdAt;
// 수정일자
@LastModifiedDate // 마지막 수정일자임을 나타냅니다.
private LocalDateTime modifiedAt;
}
2. Course 클래스에 상속 extends
Timestamped 추가
class Course extends Timestamped {
3. http://localhost:8080/h2-console 접속해서 확인
1. Week02Application 재시작!
2. course 조회
@EnableJpaAuditing
@SpringBootApplication
public class Week2Application {
public static void main(String[] args) {
SpringApplication.run(Week2Application.class, args);
}
// Week02Application.java 의 main 함수 아래에 붙여주세요.
@Bean
public CommandLineRunner demo(CourseRepository repository) {
return (args) -> {
// 데이터 저장하기
repository.save(new Course("프론트엔드의 꽃, 리액트", "임민영"));
// 데이터 전부 조회하기
List<Course> courseList = repository.findAll();
for (int i = 0; i < courseList.size(); i++) {
Course course = courseList.get(i);
System.out.println(course.getId());
System.out.println(course.getTitle());
System.out.println(course.getTutor());
}
// 데이터 하나 조회하기
// repository : SQL하는 것 | findById() : Id로 찾아라 -> Id Long타입이라 L 붙이기
Course course = repository.findById(1L).orElseThrow(
//.orElseThrow => 만약에 그런 게 없다면(뭔가 오류가 발생했을 때) 이렇게 해라 {}안에 작성하기
// () -> new NullPointerException("해당 아이디가 존재하지 않습니다.") : 포인터(가리키는 것)가 없을 때 대처 방법
() -> new NullPointerException("해당 아이디가 존재하지 않습니다.")
// () -> new IllegalArgumentException("해당 아이디가 존재하지 않습니다.")
);
};
}
}
.findById(1L)
.findById(2L)
update, delete 로 넘어가기 전에 Service에 대해 알아보자
스프링의 구조는 크게 3가지로 나눌 수 있는 데
- Controller : 가장 바깥 부분, 요청/응답을 처리함 => 요청을 받아주는 자동 응답기
- Service : 중간 부분, 실제 중요한 작동이 많이 일어나는 부분
- Repo(repository) : 가장 안쪽 부분, DB와 맞닿아 있음.
실제로 DB를 꺼내오는 것과 응답하는 것 사이에 연결고리가 필요한데 그것을 Service
라고 부름
Update는 밖에서 요청이 들어옴으로 repo까지 전달을 해주는 것이 필요하다.
그 때 우리는 중간에 있는 Service를 활용을 한다. 그래서 Update는 Service부분에 작성을 한다.
public void update(Course course) {
this.title = course.title;
this.tutor = course.tutor;
}
2. src > main > java > com.sparta.week02 > service 패키지 생성
3. CourseService.java 만들기
@Service // 스프링에게 이 클래스는 서비스임을 명시
public class CourseService {
// final: 서비스에게 꼭 필요한 녀석임을 명시
// final은 한 번 값이 부여되면 변경될 수 없음
private final CourseRepository courseRepository;
// 생성자를 통해, Service 클래스를 만들 때 꼭 Repository를 넣어주도록
// 스프링에게 알려줌
public CourseService(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
@Transactional // SQL 쿼리가 일어나야 함을 스프링에게 알려줌
// 여기 Course안에는 title과 tutor정보가 들어있는데
// 먼저 해야할 것은 찾아야겠죠?
// repo를 이용해서 Id 이용해서 찾고 만약에 없으면 알려주기
// Update할 때 필요한 것 : 업데이트할 대상의 Id와 업데이트할 내용을 정보
public Long update(Long id, Course course) {
Course course1 = courseRepository.findById(id).orElseThrow(
() -> new IllegalArgumentException("해당 아이디가 존재하지 않습니다.")
);
// 그래서 그 아이디에 해당하는 게 1이라면 course1에 들어 있을 것이고
// 2이라면 course1에 들어 있을 것이다
// 그 상태에서 course1이 Update되도록 메소드를 설정을 하면
// 우리가 전달받은 이 코스 정보가 파라미터 즉 정보로 넘어간다
// 그래서 업데이트가 되고
course1.update(course);
// 우리가 업데이트 해준 것에 아이디를 돌려줘라
return course1.getId();
}
}
4. update 실행해보기
public static void main(String[] args) {
SpringApplication.run(Week2Application.class, args);
}
@Bean
public CommandLineRunner demo(CourseRepository courseRepository, CourseService courseService) {
return (args) -> {
// 먼저 데이터 저장을 한다
courseRepository.save(new Course("프론트엔드의 꽃, 리액트", "임민영"));
System.out.println("데이터 인쇄");
// 이것을 찾아서 인쇄를 한다
List<Course> courseList = courseRepository.findAll();
for (int i=0; i<courseList.size(); i++) {
Course course = courseList.get(i);
System.out.println(course.getId());
System.out.println(course.getTitle());
System.out.println(course.getTutor());
}
// tutor 이름은 똑같고 강의 제목만 바뀐 형태로 new Course를 하나 만든 것
// 변경용으로 쓰일 예정
Course new_course = new Course("웹개발의 봄, Spring", "임민영");
// 그래서 courseService의 update기능 활용
// id가 1인 것을 가지고 변경할 코스를 넘겨줌줌
courseService.update(1L, new_course);
// 그 다음 전체 검색을 해서 제대로 바뀌었는 지 검사하기
courseList = courseRepository.findAll();
for (int i=0; i<courseList.size(); i++) {
Course course = courseList.get(i);
System.out.println(course.getId());
System.out.println(course.getTitle());
System.out.println(course.getTutor());
}
};
}
출력
// deleteAll() : 기존에 아이디 다 삭제
// deleteById() : 특정 아이디 삭제
courseRepository.deleteAll();
1. 환경 설정 들어가서
- Windows: Ctrl + Alt + S
- macOS: command + ,
2.검색창에 "Annotation Processors" 입력 후, 우측 "Enable ~" 체크하고 OK 클릭
3. Shift 두 번 누르고 plugins 입력 후 엔터
4. lombok 입력 후 아래처럼 생긴 아이콘의 우측 Install 버튼 클릭 → 인텔리제이 재시작
Course.java
@Getter // 자동으로 Getter 생성
@NoArgsConstructor // 기본생성자를 대신 생성해줍니다.
@Entity // 테이블임을 나타냅니다.
public class Course extends Timestamped {
CourseService.java
@RequiredArgsConstructor // 생성자 자동 생성
@Service // 스프링에게 이 클래스는 서비스임을 명시
public class CourseService {
// final: 서비스에게 꼭 필요한 녀석임을 명시
// final은 한 번 값이 부여되면 변경될 수 없음
// 내가 꼭 필요한 변수는 final로 선언하고
// 그 위에 @RequiredArgsConstructor 써주면 아무 메소드에서나 자유롭게 사용 가능
private final CourseRepository courseRepository;
💡 테이블을 막 건드려도 될까?
= read, update할 때 Course 클래스를 막 써도 될까?
= Course 클래스를 건들이는 것은 DB가 변경될 가능성이 커짐
그러다 보니 DB에 연결된 클래스는 그대로 두고 우리가 정보를 물고 다니는 것을 따로 만들 필요성이 부각됨
=> 그 때 필요한 것이DTO
1. src > main > java > com.sparta.week02 > models 에 CourseRequestDto 파일 생성
2. CourseRequestDto.java
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@RequiredArgsConstructor
public class CourseRequestDto {
private final String title;
private final String tutor;
}
3.CourseService 변경하기
@RequiredArgsConstructor
@Service
public class CourseService {
private final CourseRepository courseRepository;
@Transactional
public Long update(Long id, CourseRequestDto requestDto) {
Course course1 = courseRepository.findById(id).orElseThrow(
() -> new IllegalArgumentException("해당 아이디가 존재하지 않습니다.")
);
course1.update(requestDto);
return course1.getId();
}
}
4. Course 변경하기
public void update(CourseRequestDto requestDto) {
this.title = requestDto.getTitle();
this.tutor = requestDto.getTutor();
}
5. Week02Application 변경하기
CourseRequestDto requestDto = new CourseRequestDto("웹개발의 봄, Spring", "임민영");
courseService.update(1L, requestDto);
courseList = courseRepository.findAll();
for (int i=0; i<courseList.size(); i++) {
Course course = courseList.get(i);
System.out.println(course.getId());
System.out.println(course.getTitle());
System.out.println(course.getTutor());
}
CourseController.java
파일을 생성CourseController.java
import java.util.List;
// 그리고 이 final이 필요하면 반드시 사용할 때 넣어줘
// => @RequiredArgsConstructor 활용
@RequiredArgsConstructor
// JSON으로 응답해야되니까 @RestController로 표시
@RestController
public class CourseController {
// 그래서 courseRepository를 멤버변수로 하고 꼭 필요하니까 final로 정의
private final CourseRepository courseRepository;
// localhost:8080뒤에 "/api/courses"라는 주소로
// Get방식으로 조회 요청이 오면 이 메서드로 실행해라
@GetMapping("/api/courses")
// Course의 List를 반환하는 데 courseRepository(SQL)를 활용해서
// 데이터에서 전부 Course 목록 조회해서 반환
public List<Course> getCourses() {
// courseRepository에서 findAll()해서 전부 다 찾아서 돌려줘라
// 그 형태는 List<Course>다
// 그러려면 courseRepository가 필요
return courseRepository.findAll();
}
}
4. http://localhost:8080/api/courses 접속해서 확인
API를 만들고 나면 실제로 동작하는 지 확인하는 두 가지 방법
1. 테스트 코드 작성
2. 툴 사용하기
CourseController.java
> Post private final CourseService courseService;
// PostMapping을 통해서, 같은 주소라도 방식이 다름을 구분합니다.
// 생성이니까 POST방식으로 Mapping을 하겠다.
// Post방식으로 이 주소로 오면 이 메서드가 실행된다
@PostMapping("/api/courses")
// createCourse이 메서드는 기본적으로 생성한 Course를 반환함
// 정보를 물고 다니는 CourseRequestDto와 똑같은 형태로 받아
// 컨트롤러에서 요청을 받는 것이다라고 표시하기 위해 앞에 @RequestBody에 붙임
// @RequestBody 없으면 요청한 정보가 CourseRequestDto안에 들어가지 않음
// 그래서 Spring은 요청을 주고 받는 형식을 강제한다라고 한 건
// Post 혹은 Put이나 데이터를 주고받을 때 API에서 넘어오는 데이터를 잘 받으려면
// RequestBody 형태로 받아줘야함
// 그래서 이렇게 하면 requestDto안에 들어가 있음
public Course createCourse(@RequestBody CourseRequestDto requestDto) {
// requestDto 는, 생성 요청을 의미합니다.
// 강의 정보를 만들기 위해서는 강의 제목과 튜터 이름이 필요하잖아요?
// 그 정보를 가져오는 녀석입니다.
// 저장하는 것은 Dto가 아니라 Course이니, Dto의 정보를 course에 담아야 합니다.
// 잠시 뒤 새로운 생성자를 만듭니다.
// 그러고 나서 우리가 해야할 일은 새로운 Course를 만들어서
Course course = new Course(requestDto);
// JPA를 이용하여 DB에 저장하고, 그 결과를 반환합니다.
// Repository.save()함수를 이용해서 넣어주는 것
return courseRepository.save(course);
// 그럼 필요한 프로세스가 뭘까?
// 새로운 코스를 만들때 CourseDto를 이용해서 만드는 것이 필요
// >domain> course.java에서 만들어 주자!
}
Course
클래스에 생성자 추가public Course(CourseRequestDto requestDto) {
this.title = requestDto.getTitle();
this.tutor = requestDto.getTutor();
}
이렇게 해서 ARC를 실행해보면 "Bad Request"라고 에러가 남!
WHY ? > Spring POST방식에서 데이터를 전달하는 방식은 엄격하게 정해져 있기 때문
// api/courese에 id값 넣어서 수정하는 요청이 오면 update메소드 실행해
// {유동적(변형)인 값(변수명)}
@PutMapping("/api/courses/{id}")
// @PathVariable : 중괄호에서 감싸준 값이야라고 나타냄
public Long updateCourse(@PathVariable Long id, @RequestBody CourseRequestDto requestDto) {
return courseService.update(id, requestDto);
}
{
"title": "앱개발 종합반",
"tutor": "강지영"
}
5. 결과 확인
@DeleteMapping("/api/courese/{id}")
public Long deleteCourse(@PathVariable Long id){
// courseRepository.deleteById()는 아무것도 반환을 해주지 않으므로
// id를 리턴해줘야한다
courseRepository.deleteById(id);
return id;
}```
엄청나시군요. 감사합니다.