🌱 이번 장의 스터디 범위
- JPA / Hibernate / Spring Data Jpa의 관계
- Spring Data Jpa를 이용하여 관계형 데이터베이스를 객체지향적으로 관리하는 방법
🌱 JPA 소개
- 객체를 관계형 데이터베이스에서 관리하는 것이 중요
- 관계형 데이터베이스가 SQL만 인식할 수 있어 프로젝트의 대부분이 SQL로 채워지며 SQL을 피할 수 없음
- 반복적인 SQL을 만드는 것을 통해 단순 반복 작업의 문제가 생김
- 어떻게 데이터를 저장할지에 대해 패러다임 불일치 문제가 생김
- 관계형 데이터베이스와 객체지향 프로그래밍 언어의 패러다임이 달라 객체를 데이터베이스에 저장하려고 하니 문제 발생
- JPA는 지향하는 바가 다른 객체지향 프로그래밍과 관계형 데이터베이스의 중간에서 패러다임을 일치시켜주기 위한 기술
- 개발자는 객체지향적으로 프로그래밍을 하고, JPA가 이를 관계형 데이터베이스에 맞게 SQL을 대신 생성해서 실행
- 개발자는 항상 객체지향적으로 코드를 표현할 수 있으니 더는 SQL에 종속적인 개발을 하지 않아도 됨
🌱 Spring Data JPA
- 인터페이스인 JPA를 사용하기 위해서는 구현체가 필요
- 구현체들을 좀 더 쉽게 사용하고자 추상화시킨 Spring Data JPA 모듈을 이용하여 JPA 기술을 다룸
- JPA ← 구현체 Hibernate ← Spring Data JPA
- Spring Data JPA가 등장한 이유 1. 구현제 교체의 용이성 : Hibernate 외에 다른 구현체 (Eclipse, Link 등)로 교체 가능
- Spring Data JPA가 등장한 이유 2. 저장소 교체의 용이성 : 관계형 데이터베이스 외에 다른 저장소 (MongoDB 등)로 교체 가능
- Spring Data의 하위 프로젝트들의 기본적인 CRUD는 같기 때문에 의존성 등만 교체하면 쉽게 교체 가능
🌱 요구사항 분석
- 3장에서 6장까지 하나의 게시판 제작
- 게시판 기능 - 게시글 조회, 등록, 수정, 삭제
- 회원 기능 - 구글/네이버 로그인, 로그인한 사용자 글 작성 권한, 본인 작성 글에 대한 권한 관리
🌱 프로젝트에서 Spring Data JPA 적용하기
- build.gradle 의존성 등록
// build.gradle
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.projectlombok:lombok')
/* 스프링 부트용 Spring Data JPA 추상화 라이브러리
스프링 부트 버전에 맞춰 다음으로 JPA 관련 라이브러리 버전 관리 */
compile('org.springframework.boot:spring-boot-starter-data-jpa')
/* 인메모리 관계형 데이터베이스
별도의 설치 없이 프로젝트 의존성만으로 관리 */
compile('com.h2database:h2')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
- domain 패키지 생성 : 도메인을 담을 패키지
- 도메인이란 게시글, 댓글, 회원, 정산, 결제 등 소프트웨어에 대한 요구사항 혹은 문제 영역
- domain 패키지에 posts 패키지와 Posts 클래스 생성
- Posts 클래스 작성
- 필수 (주요) 어노테이션인 @Entity를 롬복의 어노테이션보다 클래스에 가깝게 작성
- JAVA 외의 다른 언어로 전환 시 롬복이 필요 없으므로 쉽게 삭제 가능
- Posts 클래스는 실제 DB의 테이블과 매칭될 클래스로 Entity 클래스라고도 함
- JPA를 사용하면 DB 데이터에 실제 쿼리를 날리기보다는 Entity 클래스의 수정을 통해 작업
- 롬복은 서비스 초기 구축 단계에서 테이블 설계가 빈번하게 변경될 때 코드 변경량을 최소화시켜줌
// Posts.java
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Getter // 롬복 어노테이션 - 클래스 내의 모든 필드의 Getter 메소드를 자동생성
@NoArgsConstructor // 롬복 어노테이션 - 기본 생성자 자동 추가, public Posts() {}와 같은 효과
@Entity // JPA 어노테이션 - 테이블과 링크될 클래스임을 나타냄
public class Posts {
@Id // JPA 어노테이션 - 해당 테이블의 PK (기본키) 필드
@GeneratedValue(strategy = GenerationType.IDENTITY) // JPA 어노테이션 - PK의 생성 규칙
private Long id;
@Column(length = 500, nullable = false) // JPA 어노테이션 - 테이블의 칼럼
private String title;
@Column(columnDefinition = "TEXT", nullable = false)
private String content;
private String author;
@Builder // 롬복 어노테이션 - 해당 클래스의 빌더 패턴 클래스 생성
public Posts(String title, String content, String author) {
this.title = title;
this.content = content;
this.author = author;
}
}
- Entity 클래스에서는 절대 Setter 메소드를 사용하지 않고 대신 목적과 의도를 나타낼 수 있는 메소드를 사용
- Setter가 없는 대신 생성자를 통해 DB에 삽입하거나 해당 이벤트에 맞는 public 메소드를 호출하여 변경
- 이 책에서는 생성자 대신 @Builder를 통해 제공되는 빌더 클래스 사용
- 채워야 할 필드가 무엇인지 명확히 지정할 수 없는 생성자와 달리 빌더는 어느 필드에 어떤 값을 채워야할지 명확하게 인지 가능
// 잘못된 사용 예 - JPA에서는 Setter 메소드 사용 X
public class Order {
public void setStatus(boolean status) {
this.status = status
}
}
public void 주문서비스의_취소이벤트() {
order.setStatus(false);
}
// 올바른 사용 예 - 목적과 의도를 나타낼 수 있는 cancelOrder 메소드 추가 O
public class Order {
public void cancelOrder(boolean status) {
this.status = status
}
}
public void 주문서비스의_취소이벤트() {
order.cancelOrder(false);
}
- JpaRepository인 PostRepository 인터페이스 생성 : Posts 클래스로 Database에 접근하게 해주는 인터페이스
- JPA의 Repository는 DB Layer 접근자
- Entity 클래스와 기본 Entity Repository는 함께 위치해야 하므로 도메인 패키지에서 함께 관리
// PostsRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
// JpaRepository<Entity 클래스, PK 타입>을 상속 - CRUD 메소드 자동 생성
public interface PostsRepository extends JpaRepository<Posts, Long> {
}
🌱 Spring Data JPA 테스트 코드 작성하기
- domain 패키지에 posts 패키지와 PostsRepositoryTest 클래스 생성
- save, findAll 기능을 테스트하는 PostsRepositoryTest 클래스
// PostsRepositoryTest.java
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest() // H2 데이터베이스(인모메리 관계형 데이터베이스)를 자동으로 실행
public class PostsRepositoryTest {
@Autowired
PostRepository postRepository;
// Junit에서 단위 테스트가 끝날 때마다 수행되는 메소드를 지정, 테스트간 데이터 침범을 막음
@After
public void cleanup() {
// 다음 테스트 실행 시를 위해 데이터 삭제
postRepository.deleteAll();
}
@Test
public void 게시글저장_불러오기() {
// given
String title = "테스트 게시글";
String content = "테스트 본문";
/* 테이블 posts에 insert/updata 쿼리를 실행
id 값이 있다면 update, 없다면 insert 쿼리 실행 */
postRepository.save(Posts.builder()
.title(title)
.content(content)
.author("wn8925@sookmyung.ac.kr")
.build());
// when
// 테이블 posts에 있는 모든 데이터를 조회해오는 메소드
List<Posts> postsList = postRepository.findAll();
// then
Posts posts = postsList.get(0);
assertThat(posts.getTitle()).isEqualTo(title);
assertThat(posts.getContent()).isEqualTo(content);
}
}
- PostsRepositoryTest 테스트 실행 및 통과 확인
- application.properties 파일로 실제로 실행된 쿼리인 쿼리 로그를 콘솔에서 확인하기
// application.properties
spring.jpa.show_sql=true
- create table 쿼리에 id bigint generated by default as identity라는 옵션으로 생성 → H2의 쿼리 문법이 적용되었기 때문
- H2는 MySQL의 쿼리를 수행해도 정상적으로 작동하기 때문에 이후 디버깅을 위해 출력되는 쿼리 로그를 MySQL버전으로 변경
// application.properties
spring.jpa.show_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
- MySQL 쿼리 로그 확인
'Java-Spring > 스프링 부트와 AWS로 혼자 구현하는 웹 서비스' 카테고리의 다른 글
[Spring Boot] Spring 웹계층 (0) | 2021.10.06 |
---|---|
[Spring Boot] 03장. 스프링 부트에서 JPA로 데이터베이스 다뤄보자 - JPA Auditing (0) | 2021.10.06 |
[Spring Boot] 02장. 스프링 부트에서 테스트 코드를 작성하자 (0) | 2021.10.02 |
[Spring Boot] 01장. 인텔리제이로 스프링 부트 시작하기 (0) | 2021.10.02 |
[Spring Boot] 00장. MVC의 역할과 실행 흐름 (0) | 2021.10.01 |