스프링 부트와 AWS로 혼자 구현하는 웹 서비스
: 인텔리제이, JPA, JUnit 테스트, 그레이들, 소셜 로그인, AWS 인프라로 무중단 배포까지
이동욱 저
3장 스프링부트에서 JPA로 데이터베이스 다뤄보자
Table of Content
MyBatis(iBatis)→JPA 개요
•
과거(MyBatis 를 사용하는 환경)에는 실제로 개발하는 시간보다 SQL 맵퍼를 통해 쿼리를 작성, 다루는 시간이 더 많았다. 또한 Java를 배우면서 객체지향 프로그래밍을 배웠는데 객체지향 프로그래밍보다는 테이블 모델링에 집중해야했다.
◦
하지만 아직도 Spring, MyBatis 환경을 많이 사용하고 있다.
•
객체를 테이블에 맞추어 데이터 전달 역할만 하는 형태로 개발을 하다보니 전반적인 Java를 통한 서비스 개발의 방향(패러다임)의 불일치가 발생했다.
•
이 패러다임 불일치를 해소하고 객체지향 프로그래밍을 데이터베이스에 적용 할 수 있는 JPA 기술을 사용하고자 한다.
JPA 적용해보기
의존성 추가
build.gradle 에 JPA 의존성을 추가하고 테스트용 DB인 H2도 설치해준다.
...
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// in-memory H2 DB for testing
implementation 'com.h2database:h2'
}
...
Java
복사
Posts.java 작성
domain.posts 패키지를 생성하고 내부에 게시글 클래스인 Posts.java를 생성한다.
package com.citefred.ldwspring.domain.posts;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@Entity
public class Posts {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 500, nullable = false)
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;
}
}
Java
복사
@Entity
•
데이터베이스 테이블과 링크될 클래스임을 나타냄
•
기본값으로 클래스의 카멜케이스 이름을 언더스코어(스네이크)(_)로 테이블 이름을 매칭
◦
ex) SalesManager.java → sales_manager 테이블
@Id
•
해당 테이블의 PK 필드를 나타냄
@GeneratedValue
•
PK의 생성 규칙을 나타냄
•
스프링 부트 2.0에서는 GenerationType.IDENTITY 옵션을 추가해야만 auto_increment가 적용
@Column
•
테이블의 컬럼을 나타내며 굳이 선언하지 않더라도 해당 클래스의 필드는 모두 컬럼이 된다.
•
사용하는 이유는 기본값 외 추가적인 필요 옵션이 있는 경우에 사용
•
문자열의 경우 VARCHAR(255)가 기본값인데 사이즈를 500으로 늘리고 싶거나, 타입을 varchar가 아닌 더 큰 TEXT로 변경하는 등 사용 함
@Builder
•
롬복의 어노테이션임 해당 클래스의 빌더 패턴 클래스를 생성
•
생성자 상단에 선언 시 생성자에 포함된 필드만 빌더에 포함
이전과 다르게 @Setter 메소드가 없다?
•
Setter는 해당 클래스 인스턴스의 값들이 언제 어디서 변해야하는지 명확하게 구분 할 수 없다 → 값이 변경되는것을 예측 불가 할 수 있음
•
그래서 Entity 클래스에서는 절대 Setter 메소드를 만들지 않는다.
◦
→ 가끔 테스트 코드작성할때 IDE는 Setter를 만들도록 권장하는데 이것을 조심해야하고 Mock모의 객체 등을 통해 해결해야 한다.
JpaRepository 생성
데이터베이스에 접근하게 해 줄 JpaRepository를 생성해야 한다.
package com.citefred.ldwspring.domain.posts;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostsRepository extends JpaRepository<Posts, Long> {
}
Java
복사
DB 계층 접근
•
보통 iBatis, MyBatis 에서 Dao라 불리는 DB Layer 접근자를 JPA에서는 Repository라고 부르며 인터페이스로 생성한다. JpaRepository 클래스를 상속받아 구현한다. 내부적으로 SimpleJpaRepository를 통해서 자동적으로 CRUD 메소드가 생성된다.
•
@Repository 어노테이션을 추가할 필요도 없다.
•
주의할 점은 Entity클래스와 기본 Entity Repository는 함께 위치해야 한다. 둘은 밀접한 관계이며 기본 Repository없이는 제대로 역할을 할 수 없기 때문이다.
◦
추후 프로젝트 규모가 커져 도메인별로 분리해야 한다면
◦
Entity클래스와 기본 Repository는 함께 움직여야 하므로 도메인 패키지에서 함께 관리한다.
Spring Data JPA 테스트 코드 작성
PostsRepositoryTest.java 를 통해 JPA 테스트하기
package com.citefred.ldwspring.domain.posts;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class PostsRepositoryTest {
@Autowired
PostsRepository postsRepository;
//@After
@AfterEach
public void cleanup(){
postsRepository.deleteAll();
}
@Test
public void 게시글저장_불러오기(){
//given
String title = "테스트 게시글";
String content = "테스트 본문";
postsRepository.save(Posts.builder()
.title(title)
.content(content)
.author("citefred@gmail.com")
.build());
//when
List<Posts> postsList = postsRepository.findAll();
//then
Posts posts = postsList.get(0);
assertThat(posts.getTitle().equals(title));
assertThat(posts.getContent().equals(content));
}
}
Java
복사
@After , @AfterEach
•
단위 테스트가 끝날 때 마다 수행되는 메소드를 지정하는 어노테이션
•
여러 테스트가 동시 수행되면 DB에 데이터가 남아있어 다음 테스트에 실패할 수 있기 때문에 초기화해주는 역할
•
교재에는 @After가 사용되었지만 JUnit5에서는 @AfterEach 로 변경되었다.
posts.Repository.save()
•
테이블 posts에 insert/update 쿼리를 실행
•
id값이 있다면 update가, 없으면 insert 쿼리가 실행
postsRepository.findAll()
•
테이블 posts에 있는 모든 데이터를 조회(read)
테스트코드 실행
실제 쿼리문 확인하기
•
application.properties에 spring.jpa.show-sql=true 를 추가하면 JPA로 실행된 쿼리문을 콘솔에서 확인 할 수 있다.
Related Posts
Search