본문 바로가기

WINK-(Web & App)/Spring Boot 스터디

[2024 Spring Boot 스터디] 정호용 #3 주차 - 5~6장 (미완)

반응형

05장. 데이터베이스 조작이 편해지는 ORM

5.1 데이터베이스란?

데이터를 매우 효율적으로 보관하고 꺼내볼 수 있는 곳이다.

이점? -> 많은 사람 안전하게 데이터를 사용/관리할 수 있다.

DBMS

데이터베이스를 관리하기 위한 소프트웨어DBMS(DataBase Management System) 이라 한다.

데이터베이스 -> 많은 사람이 사용 -> 동시 접근이 가능해야 함.

DBMS -> 위와 같은 요구사항들을 만족 & 효율적으로 데이터베이스를 관리/운영함.

MySQL, Oracle 등이 DBMS이다.

종류에 따라 관계형, 객체-관계형, 도큐먼트형, 비관계형 등으로 나뉜다.

 

관계형 DBMS

relational DBMS, 즉 RDBMS이라 한다.

관계형 모델을 기반으로 한다. (즉 테이블 형태로 이루어진 데이터 저장소를 관리)

 

H2, MySQL

H2 -> 자바로 작성되어 있는 RDBMS

즉, 스프링부트가 지원하는 인메모리 관계형 데이터베이스

애플리케이션 자체 내부에 데이터를 저장함. -> 애플리케이션 재실행 시 데이터는 초기화된다.

개발 시 테스트 용도로 사용된다.

 

MySQL -> 실제 서비스로 올릴 시 사용 예정

 

데이터베이스 기본 용어

용어명 설명
테이블 데이터를 구성하기 위한 가장 기본적인 단위, 테이블은 행-열 구성, 행은 여러 속성으로 구성
테이블의 구성 요소 중 하나, 가로로 배열된 데이터의 집합, 고유한 식별자인 기본키를 가짐, 레코드라고도 부름
테이블의 구성 요소 중 하나, 행에 저장되는 유형의 데이터, 각 요소에 대한 속성을 나타내며, 무결성 보장
기본키 행을 구분할 수 있는 식별자
쿼리 데이터베이스에서 데이터 조회, 생성, 삭제, 수정같은 처리를 하기위해 사용하는 명령문

 

SQL문으로 데이터베이스 조작하는 연습하기

데이터 조회 - SELECT문

SELECT <무엇을?>
FROM <어디에서?>
WHERE <무슨?>

 

조건 넣기 - WHERE절

자주 쓰는 조건 명령어

명령어 설명 예시
= 특정값과 동일한 값을 가진 행 조회 age = 10
!= 또는 <> 특정값과 동일하지 않은 행 조회 age != 10
<, >, <=, >= 특정 값과 대소비교 age > 10 , age <= 10
BETWEEN 지정된 값의 사이값 조회 age BETWEEN 10 AND 20
LIKE 패턴 매칭 위해 사용, %사용 시 와일드 카드 사용 name LIKE '김%'
AND 두 조건 모두 참일 시 조회 name LIKE '김%' AND '이%'
OR 둘 중 하나라도 참일 시 조회 name LIKE '김%' OR '이%'
IS NULL, IS NOT NULL NULL 값 존재여부 조회 name IS NULL

 

데이터 추가하기 - INSERT 문

INSERT INTO <어디에?>
VALUES <무슨 값을?>;

 

데이터 삭제하기 - DELETE 문

DELETE FROM <어디에서> WHERE <어떤 조건으로?>;

 

데이터 수정하기 - UPDATE문

UPDATE <어디에?>
SET <무슨 칼럼을? = 어떤 값으로?>
WHERE <어떤 조건으로?>;

 

5.2 ORM이란?

ORM(Object-Relational Mapping) -> 자바의 객체와 데이터베이스를 연결하는 프로그래밍 기법

ORM이 있다면 데이터베이스의 값을 마치 객체처럼 쓸 수 있다.

 

장점

1. SQL을 직접 작성하지 않고 사용하는 언어로 데이터베이스 접근 가능

2. 객체지향적으로 코드 작성 가능, 비즈니스 로직에만 집중 가능

3. 데이터베이스 시스템이 추상화되어 있어 MySQL에서 PostgreSQL로 전환 시에도 추가작업이 거의 없음

4. 매핑 정보가 명확 -> ERD에 대한 의존도 낮춤 / 유지보수 용이

 

단점 

1. 프로젝트 복잡성 증가 시 사용 난이도도 증가

2. 복잡하고 무거운 쿼리는 ORM으로 해결 불가능할 수도 있음

 

5.3 JPA와 하이버네이트

ORM에도 여러 종류가 있다. 자바에서는 JPA (Java Persistence API)를 표준으로 쓴다. 즉, JPA는 자바에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이다. 실제 사용을 위해서는 ORM 프레임워크를 추가로 선택해야 한다. 대표적으로는 hibernate를 많이 사용한다. 하이버네이트는 JPA 인터페이스를 구현한 구현체이자 자바용 ORM 프레임워크이다. 내부적으로는 JDBC API를 사용한다.

즉, 하이버네이트는 자바 객체를 통해 데이터베이스 종류에 상관없이 데이터베이스를 자유자재로 사용하게 해 준다.

 

엔티티 매니저란?

엔티티 : 데이터베이스의 테이블과 매핑되는 객체를 의미

본질적으로는 자바 객체이지만, 데이터베이스의 테이블과 직접 연결된다.

 

엔티티 매니저 : 엔티티를 관리해 데이터베이스와 애플리케이션 사이에서 객체를 생성/수정/삭제하는 역할을 한다. 

이런 엔티티 매니저를 만드는 곳이 엔티티 매니저 팩토리이다.

 

스프링 부트에서는 내부에서 엔티티 매니저 팩토리를 하나만 생성해서 관리하고,

@Persistence Context 또는 @Autowired 애너테이션을 사용해서 엔티티 매니저를 사용한다.

 

한편, 스프링부트는 기본적으로 빈을 하나만 생성/공유하므로 동시성 문제가 발생할 수도 있다.

실제로는 엔티티 매니저가 아닌, 실제 엔티티 매니저와 연결하는 프록시, 즉 가짜 엔티티 매니저를 사용한다.

 

영속성 컨텍스트란?

영속성 컨텍스트 : JPA의 중요한 특징 중 하나로, 엔티티를 관리하는 가상의 공간

이것이 있기에 데이터베이스에서 효과적으로 데이터를 가져올 수 있고, 엔티티를 편하게 쓸 수 있다.

 

영속성 컨텍스트의 특징 :

 

1차 캐시 :

영속성 컨텍스트 내부에 1차 캐시를 갖고 있다.

캐시의 키 -> 엔티티의 @Id 애너테이션이 달린 기본키 역할을 하는 식별자

값 -> 엔티티

 

엔티티 조회 시 ->

1차 캐시에서 데이터 조회, 값이 있으면 반환

값이 없으면 데이터베이스에서 조회, 1차 캐시에 저장, 반환

 

즉, 값 조회 시 데이터베이스를 거치치 않아도 됨

 

 

쓰기 지연 :

트랜젝션을 커밋하기 전까지는 데이터베이스에 실제 질의문을 보내지 않고,

쿼리를 모았다가,

커밋 시 한번에 실행

 

즉, 데이터베이스 시스템의 부담을 줄일 수 있다.

 

 

변경 감지 :

트랜젝션 커밋 시 1차 캐시에 저장된 엔티티 값과 현재 엔티티 값을 비교,

변경된 값이 있다면 변경 사항 감지해 변경된 값을 데이터베이스에 자동 반영

 

역시 데이터베이스 시스템의 부담 줄일수 있음.

 

 

지연 로딩 :

쿼리로 요청한 데이터를 애플리케이션에 바로 로딩x

필요할 때 쿼리를 날려 데이터 조회

엔티티의 상태

엔티티는 4가지 상태를 가진다.

영속성 컨텍스트가 관리하지 않는 분리 상태

영속성 컨텍스트가 관리하는 관리 상태

영속성 컨텍스트와 관련없는 비영속 상태

삭제된 상태 이다.

 

엔티티를 처음 만들면 엔티티는 비영속 상태이다. -> persist() 를 통해 관리 상태로 만들 수 있으며, ->

detach()를 이용해 분리 상태를 만들 수도 있다. -> remove() 를 이용해 삭제 상태로 만들 수 있다.

5.4 스프링 데이터와 스프링 데이터 JPA

스프링 데이터는 비즈니스 로직에 더 집중할 수 있게 데이터베이스 사용 기능을 클래스 레벨에서 추상화했다.

이 인터페이스는 CRUD를 포함한 여러 메서드가 포함되어 있고, 알아서 쿼리를 만들어 준다.

각 데이터베이스의 특성에 맞춰 기능을 확장해 제공한다.

-> JPA는 스프링에서 구현한 스프링 데이터 JPA를,

MongoDB는 스프링 데이터 MongoDB를 사용한다.

 

스프링 데이터 JPA란?

스프링 데이터의 공통적인 기능에서 JPA의 유용한 기술이 추가된 것.

스프링 데이터의 인터페이스인 PagingAndSortingRepository 를 상속받아 JpaRepository 인터페이스를 만든다.

@PersistenceContext
EntityManager em;

public void join() {

	Member member = new Member(1L,"홍길동");
    em.persist(member)
    
}

메서드 호출로 엔티티 상태 변경의 예시이다. 지금까지 이렇게 써 왔다.

 

근데

 

스프링 데이터 JPA를 사용해서 간단히 만들 수 있다!

public interface MemberRepository extends JpaRepository<Member, Long> {
}

JpaRepository 인터페이스를 우리가 만든 인터페이스에서 상속받는다.

제네릭<> 에는 관리할 <엔티티 이름, 엔티티 기본키의 타입> 을 써 준다.

 

스프링 데이터 JPA에서 제공하는 메서드 써 보기

 

1단계. MemberRepository.java에서 Alt+Enter(맥에서는 option + Enter) 를 눌러 Create Test 를 누른다.

설정값을 건드리지 말고 ok한다.

 

조회 메서드

member 테이블에 있는 모든 데이터를 조회 시 이렇게 해야 한다.

SELECT * FROM member

JPA에서 데이터를 가져올 땐 쿼리 대신 findAll()을 쓰면 된다.

 

1단계. insert-members.sql을 만들고 아래와 같이 코드를 넣는다.

 

2단계. application.yml을 만들고 아래처럼 코드를 넣는다. main폴더에 있는 data.sql을 자동으로 실행하지 않게 하는 것이다.

3단계. MemberRepositoryTest.java에 아래처럼 코드를 추가한다.

@Sql 애너테이션은 테스트 실행 전  SQL스크립트를 실행하게 한다.

 

4단계. 실행해 본다.

성공

 

5단계. 디버그 모드를 이용해 객체 확인

검증문에 있는 줄 번호를 클릭 -> 빨간 원 표시

메서드 왼쪽 실행 버튼 누른 뒤 디버그 버튼 클릭

 

6단계. 세부적으로 객체 확인

members 객체를 보면 화살표 표시가 있는데, 이를 눌러주자. Members객체들이 모두 뜬다.

 

7단계. id로 멤버 찾기

SELECT * FROM member WHERE id = 2;

SQL에서는 이렇게 해야 한다!

 

근데 JPA에서는 이렇게만 하면 된다!

 

쿼리 메서드 사용해보기

id가 아닌 name으로 찾고 싶을 때는??

name은 값이 있거나, 없을 수도 있다. JPA에서는 기본적으로 name을 찾아주는 메서드를 지원하지는 않는다.

 

1단계. name의 값이 'C'인 멤버를 찾아야 하는 경우 ---> SQL은 이렇게 쓴다.

SELECT * FROM member WHERE name = 'C';

 

이런 쿼리를 동적 메서드로 만들 수 있다.

 

findByName() 메서드를 추가하면 된다. 상속받지 않고도 그냥 정의 후에 갖다 쓰면 된다.

 

2단계. MemberRepositoryTest.java 수정

테스트가 잘 작동한다. 이런 메서드를 쿼리 메서드라 한다. JPA가 정해준 메서드 이름 규칙을 따르면 쿼리문을 특별히 구현하지 않아도 메서드처럼 쓸 수 있다.

 

추가, 삭제 메서드 사용해보기

1단계. 코드 수정

JPA에서는 save()메서드를 사용한다.

또한 이미 추가된 데이터가 있으면 안 되므로 @Sql 애너테이션을 쓰지 않았다.

 

성공

 

2단계. 만약 여러 엔티티를 한꺼번에 저장하고 싶다면 saveAll() 메서드를 사용할 수도 있다.

saveAll()로 모두 저장하고, 

findAll()로 모두 찾아버리기!

 

3단계. 멤버 삭제

JPA에서는 deleteById()를 쓰면 아이디로 레코드를 삭제할 수 있다.

삭제된 데이터이므로 isEmpty()메서드로 검증한다.

findById()로 2L을 찾고,

isEmpty()로 비어있는지 확인하고,

isTrue()로 비어있는 것이 참인지 확인한다.

 

4단계. 디버그 모드로 확인

검증문에 빨간 원을 표시한 뒤, deleteMemberById()를 디버그 모드로 실행해 본다.

A와 C만이 남아있다.

 

5단계. 모든 데이터 삭제

모든 데이터 삭제 시 deleteAll()메서드를 쓰면 된다.

하지만, 이 메서드는 실제 모든 데이터를 삭제하므로 잘 안쓰인다.

보통 @AfterEach 애너테이션을 붙여 cleanUp()메서드 같은 형태로 쓴다.

@DataJpaTest
class MemberRepositoryTest {
	@Autowired
    MemberRepository memberRepository;
    
    @AfterEach
    public void cleanUp() {
    	memberRepository.deleteAll();
    }
}

 

 

수정 메서드 사용하기

UPDATE member
SET name = 'BC'
WHERE id = 2;

내용 변경시는 위처럼 UPDATE문을 써야 한다.

JPA에서 수정할 때는 조금 다른데, 트랜젝션 내에서 데이터를 수정해야 하기 때문이다.
-> 그냥 메서드만 쓰지말고, @Transactional 애너테이션을 메서드에 추가해야 한다.

 

1단계. Member.java 수정

 해당 메서드가 @Transactional 애너테이션이 포함된 메서드에서 호출되면

-> JPA는 변경 감지 기능을 통해 엔티티의 필드값이 변경될 때

-> 변경 사항을 데이터베이스에 자동으로 반영한다.

 

 

2단계. MemberRepositoryTest.java 수정

@DataJpaTest 가 @Transactional을 대신하지만, 실제 서비스 코드에서는 @Transactional 기능을 써야 한다.

 

5.5 예제 코드 살펴보기

애너테이션명 설명
@Entity Member 객체를 JPA가 관리하는 엔티티로 지정, Member 클래스와 실제 데이터베이스 테이블 매핑
PROTECTED 기본 생성자이다. 엔티티는 반드시 생성자가 있어야 한다. 접근 제어자는 public/protected 여야 한다.
@Id Long 타입의 id 필드를 테이블의 기본키로 지정
@GeneratedValue 기본키의 생성방식을 결정 (AUTO,IDENTITY,SEQUENCE,TABLE)
@Column 데이터베이스의 컬럼과 필드를 매핑해준다.

 

6장. 블로그 기획하고 API 만들기

6.1 사전 지식 : API와 REST API

API는 프로그램 간에 상호작용하기 위한 매개체를 말한다.

즉, 우리가 웹 사이트의 주소를 입력하면(구글 메인 화면을 보여줘 라고 요청하면)

-> API는 이 요청을 받아 서버에게 전달한다.

-> 서버는 API가 준 요청을 처리하고, 그 결과물을 다시 API에게 전달한다.

-> API는 최종 결과물을 브라우저에 보낸다.

-> 사용자는 구글 메인 화면을 볼 수 있게 된다.

 

REST API는 웹의 장점을 최대한 활용하는 API이다. REST는 (REpresentational State Transfer)의 줄임말인데, 자원을 이름으로 구분해 자원의 상태를 주고 받는것이다. 즉, 명확하고 이해하기 쉬운 API를 말한다.

 

REST API 의 특징

REST API는 서버/클라이언트 구조, 무상태, 캐시 처리 가능, 계층화, 인터페이스 일관성과 같은 특징이 있다.

 

REST API 의 장점과 단점

장점 :

URL만 보고도 무슨 행동을 하는 API인지 명확하게 알 수 있다.

상태가 없다는 특징이 있어서 클라이언트-서버의 역할이 분리된다.

HTTP표준을 사용하는 모든 플랫폼에서 쓸 수 있다.

 

단점 :

HTTP 메서드, GET POST 와 같은 방식의 개수에 제한이 있다.

설계를 하기 위해 공식적으로 제공되는 표준 규약이 없다.

 

하 지 만, 주소와 메서드만 보고 요청의 내용을 파악하는 엄청난 장점이 있다.

 

REST API의 사용법

규칙 1. URL에는 동사를 쓰지 말고, 자원을 표시해야 한다.

예를 들어, 학생 중에 id가 1인 학생의 정보를 가져오는 URL은 이렇게 설계한다.

1. /students/1

2. /get-student?student_id=1

REST API에 더 맞는, RESTful API는 1번이다. 

2번은 자원이 아닌 다른 표현을 섞어썼다.

 

1. /articles/1

2. /articles/show/1

3. /show/articles/1

1번 적합, 2,3번 부적합 -> show라는 동사가 있음

 

규칙 2. 동사는 HTTP 메서드로

동사는 HTTP 메서드 라는 것으로 해결한다.

주로 POST, GET, PUT, DELETE 가 사용된다. 각각 만들고(Create), 읽고(Read), 업데이트하고(Update), 삭제(Delete)하는 역할이다. 이것을 묶어 CRUD 라 한다.

설명 적합한 HTTP 메서드와 URL
id가 1인 블로그 글을 조회하는 API GET/articles/1
블로그 글을 추가하는 API POST/articles
블로그 글을 수정하는 API PUT/articles/1
블로그 글을 삭제하는 API DELETE/articles/1

 

6.2 블로그 개발을 위한 엔티티 구성하기

프로젝트 준비하기

새로 프로젝트를 만들고, application.yml과 SpringBootDeveloperApplication.java를 그대로 옮겨줬다.

 

엔티티 구성하기

컬럼명 자료형 null 허용 설명
id BIGINT N 기본키 일련번호, 기본키
title VARCHAR(255) N   게시물 제목
content VARCHAR(255) N   내용

1단계. 코드 작성 

 

위처럼 코드를 작성하자. @Builder 애너테이션은 롬복에서 지원하는 애너테이션이다. 이를 생성자 위에 입력하면 빌더 패턴 방식으로 객체 생성 가능하다.

 

2단계. getId(), getTitle() 들을 @Getter, @NoArgsConstructor 로 대치

@NoArgsConstructor -> 접근 제어자가 protected인 기본 생성자를 별도 코드 없이 생성

@Getter -> 모든 필드에 대한 접근자 메서드를 만들 수 있다.

 

리포지터리 만들기

1단계. repository  패키지 작성 및 인터페이스 생성

 

 

 

6.3 블로그 글 작성을 위한 API 구현하기

클라이언트 <-요청/응답-> 컨트롤러 <-save()-> 서비스 <-save()-> 리포지터리

순이다.

 

서비스 메서드 코드 작성하기

블로그에 글 추가하는 코드를 서비스 계층에 작성한다.

1단계. springbootdeveloper 패키지에 dto 패키지를 생성,

dto 패키지를 컨트롤러에서 요청한 본문을 받을 객체인 AddArticleRequest.java 생성

toEntity()는 빌더 패턴을 이용해 DTO를 엔티티로 만들어준다.

해당 메서드는 블로그 글 추가 시 저장할 엔티티로 쓴다.

 

2단계. service 패키지 생성 후 BlogService.java 생성

역시 코드를 작성해 준다.

 

@RequiredArgsConstructor -> 빈을 생성자로 생성하는 롬복에서 지원하는 애너테이션,

final 키워드나 @NotNull이 붙은 필드로 생성자를 만들어준다.

 

@Service -> 해당 클래스를 빈으로 서블릿 컨테이너에 등록해 준다.

 

save() -> JpaRepository에서 지원하는 메서드로, AddArticleRequest 클래스에 저장된 값을 article 데이터베이스에 저장한다.

 

컨트롤러 메서드 코드 작성하기

이제 URL에 매핑하기 위한 컨트롤러 메서드를 추가해보자.

컨트롤러 메서드에는 URL 매핑 애너테이션 @GetMapping, @PostMapping, @PutMapping, @DeleteMapping 등을 쓸 수 있다.

각 메서드는 HTTP 메서드에 대응한다. 

 

여기서는 /api/articles에

POST요청이 오면 @PostMapping을 이용해 매핑한 뒤,

블로그 글을 생성하는 BlogService의 save() 메서드를 호출한 뒤,

생성된 블로그 글을 반환하는 작업을 할 addArticle() 메서드를 작성한다.

 

 

01단계. controller 패키지 생성, 컨트롤러 메서드 작성

 

@RestController 애너테이션 => HTTP 응답으로 객체 데이터를 JSON 형식으로 반환한다.

 

@PostMapping() => HTTP 메서드가 POST일 때 요청받은 URL과 동일한 메서드와 매핑한다. 

여기서는 /api/articles는 addArticle()에 매핑한다.

 

@RequestBody 애너테이션 => HTTP 요청 시 응답에 해당하는 값을 @RequestBody 애너테이션이 붙은 대상 객체인 AddArticleRequest에 매핑한다.

 

ResponseEntity.status().body() => 응답 코드로 201, CREATED를 응답하고 테이블에 저장된 객체 반환

 

 

API 실행 테스트하기

01단계 application.yml파일 수정

 

들여쓰기 위치를 주의하여 코드를 추가한다.

 

02단계. 

 

스프링 부트 서버 실행

-> PostMan 실행

-> HTTP 메서드 POST,

URL은 http://localhost:8080/api/articles,

[BODY]는 [raw->JSON]으로 변경

-> 요청 값 작성 

-> Send 눌러 요청 보내기

 

위 과정이 HTTP 메서드 POST로 서버에 요청에 값을 저장하는 방법이다.

 

 

 

03단계.

http://localhost:8080/h2-console 에 접속

항목 모두 입력 후 connect 클릭

 

SQL 쿼리 입력후 RUN

 

 

실제 H2 데이터베이스에 저장된 데이터들이 나온다. 필자는 postman에서 send를 4번 해봐서 여기에 4개의 데이터가 뜨는 것이다.

 

반복 작업을 줄여 줄 테스트 코드 작성하기

매번 위와 같은 방법으로 테스트하면 불편할 것이다.

반복 작업을 줄여줄 테스트 코드를 작성해 보자.

 

01단계. 

테스트 생성

 

테스트 코드를 작성해 준다. ObjectMapper는 자바 객체를 JSON으로 변환하는 직렬화 또는 JSON 데이터를 자바에서 사용하기 위해 자바 객체로 변환하는 역직렬화 시 사용한다.

 

 

2단계. 테스트 코드 작성 (이어서)

3단계. 실행

 

테스트 코드를 실행한다. 잘 실행된다.

반응형