[Spring Boot]JPA 연동 - 2) 데이터 등록
이전 글에 이어 오늘은 데이터를 등록하는 과정을 포스팅했습니다.
테스트 환경
- Spring Boot 2.5.6
- JDK 1.8
- MariaDB 10.2.22
1. 의존성 주입
1-1. 라이브러리 추가
JPA 적용을 위해 라이브러리를 추가합니다.(spring-boot-starter-data-jpa)
Lombok 어노테이션을 사용을 위해 lombok 라이브러리도 추가해주었습니다.
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
1-2. Lombok 설치
(이전 글 1-2에서 lombok 의존성을 추가했다면 넘어가도 되는데 처음 프로젝트 셋팅시에는 이 부분을 몰랐네요...)
다운로드 사이트 접속 후 파일을 다운받습니다.
1-3. cmd 실행 후 다운받은 파일의 경로에서 java -jar lombok.jar 명령어 입력
1-4. Lombok을 설치할 IDE 선택
Specify location 클릭 후 lombok을 설치할 IDE를 선택합니다. Install/Update를 하면 설치가 완료됩니다.
2. 테스트 테이블 생성 & 외부설정
2-1. 데이터 CRUD를 위해 테스트 테이블을 생성합니다.
2-2. 외부설정(application.properties)
이전 글에서 외부설정에 DB연결까지 해주었고, JPA 관련 설정을 추가합니다.
application.properties
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
# DB
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/DBNAME
spring.datasource.username=root
spring.datasource.password=1234
# JPA Properties
# Hibernate DB 설정
spring.jpa.database-platform=org.hibernate.dialect.MariaDB102Dialect
# DDL 자동생성
spring.jpa.generate-ddl=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.open-in-view=false
spring.jpa.show-sql=true
# SQL 문단 정렬
spring.jpa.properties.hibernate.format_sql=true
# SQL 주석 표시
spring.jpa.properties.hibernate.use_sql_comments=true
[옵션설명]
spring.jpa.database-platform=org.hibernate.dialect.MariaDB102Dialect ☞ JPA는 기본적으로 hibernate라는 구현체를 이용하는데 hibernate는 지정되는 DB에 맞게 SQL문을 생성하는 dialect가 존재합니다. (*dialect : 데이터베이스 종류에 맞는 SQL 문법을 처리한다)
버전에 맞는 dialect는 여기서 확인할 수 있습니다.
spring.jpa.generate-ddl=false ☞ spring.jpa.hibernate.ddl-auto 옵션을 사용할지 결정하며 true 설정시, @Entity 어노테이션이 명시된 클래스를 찾아 DDL을 생성합니다.
spring.jpa.hibernate.ddl-auto=none ☞ DB 스키마 자동설정 옵션으로 다음과 같은 옵션이 있습니다.
- none : 아무것도 실행하지 않는다.(기본값)
- create-drop : 어플리케이션 실행시 테이블 생성후 종료시 테이블 삭제한다.
- create : 어플리케이션 실행시 기존 테이블을 삭제 후 재생성한다.
- update : 변경된 스키마를 적용한다.
- validate : 변경된 스키마가 있는 경우 변경사항을 출력하고 어플리케이션을 종료한다.
spring.jpa.open-in-view=false ☞ 영속성 컨텍스트를 유지하는 옵션. true 설정시 트랜잭션 범위가 끝나도 영속성 컨텍스트를 API 응답과 뷰 페이지에 렌더링될 때까지 유지합니다. 반대로 false 설정시 트랜잭션 범위 안에서만 영속성 컨텍스트를 유지합니다.
*영속성 컨텍스트 : 어플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 역할
spring.jpa.show-sql=true ☞ JPA가 생성한 SQL문의 가독성을 높여줍니다.(true or false)
spring.jpa.properties.hibernate.format_sql=true ☞ 로그에 표시되는 SQL문을 표시합니다.(true or false)
spring.jpa.properties.hibernate.use_sql_comments=true ☞ SQL문에 주석을 표시합니다.(true or false)
3. 클래스 생성
3-1. 패키지 생성 및 엔티티 클래스 추가
기존 com.example.demo 패키지에 board.entity 라는 패키지를 추가하고 Board 클래스를 생성합니다.
JPA에서는 데이터베이스의 테이블을 다루기 위해 엔티티라는 클래스를 사용합니다. 엔티티란 자바 객체로 매핑하여 인스턴스 형태로 존재하는 데이터를 말합니다. 어노테이션을 사용해 엔티티 클래스를 구성하고 엔티티 클래스를 기반으로 테이블과 매핑됩니다.
package com.example.demo.board.entity;
import javax.persistence.Id;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@Builder
@NoArgsConstructor(access=AccessLevel.PROTECTED)
@Entity(name="board")
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long boardno;
@Column(nullable = false, length = 50)
private String title;
@Column(nullable = false, length = 500)
private String content;
public Board(String title, String content) {
this.title = title;
this.content = content;
}
}
@Getter ☞ 해당 클래스에 포함된 멤버 변수의 getter 메소드를 생성합니다. Setter가 없는 이유는 클래스의 인스턴스값들이 언제 변경되는 알 수 없기 때문입니다. 따라서 Entity 클래스에서는 절대 Setter 메소드를 만들지 않습니다.
@Builder ☞ 해당 클래스의 빌더 패턴 클래스를 생성합니다.
@AllArgsConstructor ☞ 모든 멤버변수를 받는 생성자를 만들어줍니다.
@NoArgsConstructor(access=AccessLevel.PROTECTED) ☞ 기본 생성자를 자동 추가합니다. access=AccessLevel.PROTECTED : 동일 패키지 내의 클래스에서만 객체를 생성할 수 있습니다.
@Entity(name="board") ☞ 테이블과 링크될 클래스임을 뜻합니다. 기본값으로 클래스명을 카멜케이스 규칙에 의해 테이블에 매핑하게 되는데 name옵션을 사용하는 경우 테이블을 지정할 수 있습니다.
@Id ☞ 해당 변수가 PK임을 의미합니다. 이 때 Id 어노테이션의 import 주의해야 합니다. => import javax.persistence.Id;
@GeneratedValue(strategy = GenerationType.IDENTITY) ☞ PK 생성 전략을 설정합니다. 2-1에서 PK를 자동 증가하도록(AUTO_INCREMENT) 설정했기 때문에 GenerationType.IDENTITY을 사용하며, 오라클의 경우 GenerationType.SEQUENCE를 사용합니다.
@Column ☞ 컬럼 속성값을 설정합니다.(nullable = false : 널허용X, length = 50 : 길이 제한)
3-2. Repository 생성
board.entity 패키지안에 추가로 Repository 클래스를 생성합니다.
JPA에서 Repository는 Mybatis에서 Dao라고 불리는 DB 접근자입니다. interface 형태로 생성하며 JpaRepository<Entity클래스, PK타입>를 상속하면 기본적인 CRUD 기능을 사용할 수 있습니다.
PK타입을 Long으로 지정하는 이유는 PK값(boardno의 타입은 int 따라서 정수형)의 식별자가 0임을 보장하기 위해서입니다.
package com.example.demo.board.entity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.demo.board.entity.Board;
public interface BoardRepository extends JpaRepository<Board, Long> {
}
3-3. Contorller 생성
기존 com.example.demo 패키지에 board.controller 라는 패키지를 추가하고 View의 요청을 처리할 Controller를 생성합니다.
package com.example.demo.board.controller;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.board.entity.Board;
import com.example.demo.board.entity.BoardRepository;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
@RequestMapping("/board")
public class BoardController {
private final BoardRepository boardRepository;
@GetMapping("list")
public List<Board> findAllBoard() {
return boardRepository.findAll();
}
@PostMapping("new")
public Board newContents() {
final Board board = Board.builder()
.title("TEST 제목")
.content("TEST 내용")
.build();
return boardRepository.save(board);
}
}
@RestController ☞ 결과값을 JSON 형태로 반환시킵니다. 따라서 각 메소드마다 @ResponseBody를 선언하지 않아도 됩니다.
@RequiredArgsConstructor ☞ final 객체를 의존성 주입(생성자 주입)합니다.
@RequestMapping ☞ URL 요청에 대한 Controller 매핑 역할을 합니다. 여기서는 /board URL 뒤로 list가 붙는 경우는 게시글 리스트를, new가 붙는 경우는 게시글 등록을 하도록 했습니다.
4. 테스트 코드 작성
4-1. 테스트 클래스 생성
이제 Controller 코드를 테스트하기 위해 테스트 클래스를 생성합니다.(데이터 등록 테스트)
테스트 클래스는 3-2에서 생성한 Repository 클래스가 위치한 패키지와 동일하게 맞춘다. 왜냐하면 데이터 등록은 의존성을 주입한 final 객체인 BoardRepository를 통해 이뤄지기 때문입니다.
BoardRepositoryTest.java
package com.example.demo.board.entity;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
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 com.example.demo.board.entity.Board;
import com.example.demo.board.entity.BoardRepository;
@RunWith(SpringRunner.class)
@SpringBootTest
public class BoardRepositoryTest {
@Autowired
BoardRepository boardRepository;
@Test
public void postAdd() throws Exception {
String title = "테스트 제목";
String content = "안녕하세요";
boardRepository.save(Board.builder()
.title(title)
.content(content)
.build());
List<Board> list = boardRepository.findAll();
Board board = list.get(0);
assertThat(board.getTitle()).isEqualTo(title);
assertThat(board.getContent()).isEqualTo(content);
}
}
@SpringBootTest ☞ 테스트 시에 필요한 모든 의존성을 제공하는 어노테이션입니다. (Mock로 테스트할 경우 webEnvironment라는 옵션을 제공합니다)
@RunWith(SpringRunner.class) ☞ @SprintBootTest 사용시 모든 의존성을 제공받기 때문에 프로젝트 규모가 커질수록 로딩이 느려질 수 있는데 @RunWith(SpringRunner.class)를 함께 사용하면 @Autowired, @MockBean에 해당하는 자원에 대해서만 로딩하여 무게를 줄일 수 있습니다. (JUnit5에서는 사용하지 않는다)
@RunWith 에러시에는 JUnit을 build path에 추가해주면 됩니다.
@Test ☞ 테스트를 하고자하는 메소드에 지정합니다.
4-2. 테스트 실행
마우스 우클릭 - Run As - JUnit Test를 실행합니다.
JUnit Test실행 후 아무 반응이 없다면 Run As - Run Configurations에서 JUnit의 Test runner버전을 확인합니다.
(build path에 추가된 JUnit 버전과 동일하게 변경)
테스트가 성공적으로 실행되면 아래와 같이 insert문을 확인할 수 있습니다.
참고자료
+ 피드백은 언제나 환영입니다 :)