이 글은 책 「스프링 부트와 AWS로 혼자 구현하는 웹 서비스」를 공부하고 정리한 글입니다.
이번 시간에는 게시판 프로젝트에 Spring Data JPA를 적용해보자!
Spring Data JPA를 사용하기 위해서는 build.gradle에 spring-data-jpa
와 h2
에 대한 의존성을 추가해야 한다.
runtimeOnly 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
spring-boot-starter-data-jpa
h2
의존성 추가를 완료했다면 본격적으로 JPA 기능을 사용해보자!
먼저 도메인을 담을 패키지인 domain
패키지를 만든 뒤, posts
패키지와
Post
클래스를 만든다.
💡 도메인이란
게시글, 댓글, 회원, 정산, 결제 등 소프트웨어에 대한 요구사항 혹은 문제 영역을 말한다.
@Getter
@NoArgsConstructor
@Entity
public class Post {
@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 Post(String title, String content, String author) {
this.title = title;
this.content = content;
this.author = author;
}
}
여기서 Post 클래스는 실제 DB의 테이블과 매칭될 클래스이며 보통 Entity 클래스라고도 한다.
JPA를 사용하면 DB 데이터에 작업할 경우 실제 쿼리를 날리기보다는, 이 Entity 클래스의 수정을 통해 작업한다.
@Entity
_
)으로 테이블 이름을 매칭한다.SalesManager.java
→ sales_manager
table@Id
@GeneratedValue
GenerationType.IDENTITY
옵션을 추가해야만 auto_increment
가 된다.@Column
VARCHAR(255)
가 기본값인데, 500
으로 늘리고 싶거나 (ex: title
), TEXT
로 변경하고 싶거나 (ex: content
) 등의 경우에 사용된다.@NoArgsConstuctor
public Posts() {}
와 같은 효과@Getter
@Builder
서비스 초기 구축 단계에서는 테이블 설계(여기에서는 Entity 설계)가 빈번하게 변경되는데,
이때 롬복의 어노테이션들은 코드 변경량을 최소화시켜 주기 때문에 적극적으로 사용한다.
📌 참고 | Entity의 PK
- 웬만하면 Entity의 PK는 Long 타입의 Auto_increment를 추천한다.
(MySQL 기준으로 이렇게 하면 bigint 타입이 된다.)- 주민등록번호와 같이 비즈니스상 유니크 키나, 여러 키를 조합한 복합키로 PK를 잡을 경우 테이블을 하나 더 둬야하거나 PK 전체를 수정해야 하는 등 여러모로 불편하다.
→ 주민등록번호, 복합키 등은 유니크 키로 별도로 추가하는 것이 좋다.
이 책에서는 주요 어노테이션을 클래스에 가깝게 둔다. 어노테이션을 정렬하는 기준은 다음과 같다.
@Entity
는 JPA의 어노테이션이며,@Getter
와 @NoArgsConstuctor
는 롬복의 어노테이션이다.@Entity
를 클래스에 가깝게 두고,위의 코드를 잘 살펴보면 Post 클래스에는 Setter 메소드가 없다는 것을 확인할 수 있다.
자바빈 규약을 생각하면서 getter/setter를 무작정 생성하는 경우가 있는데, 이렇게 되면 해당 클래스의 인스턴스 값들이 언제 어디서 변해야 하는지 코드상으로 명확하게 구분할 수가 없어, 차후 기능 변경 시 복잡해진다.
그래서 Entity 클래스에는 절대 Setter 메소드를 만들지 않는다!
그렇다면 Setter가 없는 상황에서 값을 어떻게 채워서 DB에 삽입해야 할까?
기본적인 구조는 생성자를 통해 최종값을 채운 후 DB에 삽입하는 것이다.
만약 값 변경이 필요한 경우 해당 이벤트에 맞는 public 메소드를 호출하여 변경한다. 책에서는 생성자 대신 @Builder를 통해 제공되는 빌더 클래스를 사용한다.
Example(b, a)
와 같이 a와 b의 위치를 변경해도 코드를 실행하기 전까지는 문제를 발견할 수가 없다.
public Example(String a, String b) {
this.a = a;
this.b = b;
}
하지만 빌더를 사용하게 되면 다음과 같이 어느 필드에 어떤 값을 채워야할지 명확하게 인지할 수 있다.
Example.builder()
.a(a)
.b(b)
.build();
앞으로 모든 예제는 이렇게 빌더 패턴을 적극적으로 사용하니, 잘 익혀두는 것이 좋다!
이제 Post 클래스로 Database를 접근하게 해줄 JpaRepository를 생성해보자.
public interface PostRepository extends JpaRepository<Post, Long> {
}
인터페이스 생성 후 JpaRepository<Entity클래스, PK타입>
를 상속하면, 기본적인 CRUD 메소드가 자동으로 생성된다.
Entity는 기본 Repository 없이는 제대로 역할을 하지 못할 만큼 Entity 클래스와 기본 Entity Repository는 아주 밀접한 관계를 갖는다. 그러므로 Entity 클래스와 기본 Entity Repository는 도메인 패키지에서 함께 관리해야 한다.
@RunWith(SpringRunner.class)
@SpringBootTest
public class PostRepositoryTest {
@Autowired
PostRepository postRepository;
@After
public void cleanup() {
postRepository.deleteAll();
}
@Test
public void 게시글저장_불러오기() {
//given
String title = "테스트 게시글";
String content = "테스트 본문";
postRepository.save(Post.builder()
.title(title)
.content(content)
.author("mmy789@naver.com")
.build());
//when
List<Post> postList = postRepository.findAll();
//then
Post posts = postList.get(0);
assertThat(posts.getTitle()).isEqualTo(title);
assertThat(posts.getContent()).isEqualTo(content);
}
}
@After
postRepository.save
postRepository.findAll
별다른 설정 없이 @SpringBootTest
를 사용할 경우 H2 데이터베이스를 자동으로 실행해준다.
다음 시간에는 본격적으로 API를 만들어보자!