[Spring Data JPA] JPA Entity, Repository, Service 클래스 작성 (조회...

[Spring Data JPA] JPA Entity, Repository, Service 클래스 작성 (조회...

2021.12.12 - [Spring/Spring Data JPA] - [Spring Data JPA] 예제 프로젝트 생성 및 초기 환경 구성 - QuickStart 1

이전글의 내용을 이어서 작성합니다.

# 해당 시리즈 게시글은 Notion에서 작성된 내용을 그대로 옮겨오는 과정에서 서식의 깨짐 및 부자연스러움이 발생할 수 있습니다.

Entity 클래스 및 모델 클래스 작성

io.starter.jpatutorial.domain.jpo.PostJpo

@Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @Entity @Table(name = "post") public class PostJpo { /** * 게시글 번호 (Auto Increment) */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long no = 0L; /** * 게시글 제목 */ private String title; /** * 게시글 내용 */ private String content; /** * 게시글 작성 일시 */ private LocalDateTime createdAt = LocalDateTime.now(); /** * 조회수 */ private int views = 0; }

io.starter.jpatutorial.domain.model.Post

@Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @Builder public class Post { private Long no; private String title; private String content; private LocalDateTime createdAt; private int views; /** * Jpo -> Domain 객체 변환 */ public static Post jpoOf(PostJpo postJpo) { Post post = new Post(); BeanUtils.copyProperties(postJpo, post); return post; } /** * Domain 객체 -> Jpo 변환 */ public PostJpo asJpo() { PostJpo postJpo = new PostJpo(); BeanUtils.copyProperties(this, postJpo); return postJpo; } }

Jpo는 Java Persistence Object로 영속성 Layer에서 Database와 Application Layer에서 사용을 하기 위한 클래스입니다.

쉽게 말하여 Application 레벨에서 Database 와의 데이터 조회 및 갱신은 Entity 객체로 이루어져야 되는 것이고, View 또는 데이터가 보여지는 즉 Client와 데이터를 주고 받는 영역에서 DTO 클래스를 통해 이루어져야 됩니다.

그에 따라 Jpo ↔ Dto 클래스 간에는 속성이 흡사할 수 있고, 서로 변환이 가능한 구조로 만들어줍니다.

해당 내용을 도식화해보면 다음과 같습니다.

Repository Interface 작성

io.starter.jpatutorial.domain.repository.PostMariaRepository

@Repository public interface PostMariaRepository extends JpaRepository { }

Spring Data JPA 모듈에서 제공하는 Repository Interface는 대표적으로 CrudRepository , JpaRepository , PagingAndSortingRepository 등이 있습니다.

용도 및 환경에 따라 다를 수 있으나 일반적으로 간단하게 가장 많이 사용되는 JpaRepository 을 상속받아 Generic 구체 타입은 특정 Entity 클래스와 해당 클래스의 @Id 가 되는 필드의 타입을 선언합니다.

JpaRepository 클래스 기준으로 보면 다음과 같은 메소드들이 이미 선언이 되어있으며 이미 명칭에 따라 구현된 로직으로 실행이 됩니다.

save

saveAll

findById

findAll

exists

deleteById

deleteAll

Service 클래스 조회 로직 작성

io.starter.jpatutorial.service.PostListService

@Service @RequiredArgsConstructor public class PostListService { private final PostMariaRepository postMariaRepository; @Transactional(readOnly = true) public List fetch() { return postMariaRepository.findAll() .stream() .map(Post::jpoOf) .collect(Collectors.toList()); } }

여기까지 작성이 되고난 후 현재의 프로젝트 패키지 구조는 아래와 같습니다.

Entity 조회(Select) 테스트

io.starter.jpatutorial.JpaTutorialApplicationTests

package io.starter.jpatutorial; import io.starter.jpatutorial.domain.model.Post; import io.starter.jpatutorial.service.PostListService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest class JpaTutorialApplicationTests { @Autowired private PostListService postListService; @Test @DisplayName("게시글 목록 전체 조회 테스트") void fetch() { List posts = postListService.fetch(); Assertions.assertNotNull(posts); System.out.println("Posts --> " + posts); } }

간단하게 Junit을 활용한 테스트 코드를 작성해줌으로 손쉽게 테스트를 해볼 수 있습니다.

최초에 실행할 시 별도로 데이터베이스에서 테이블을 생성해주지 않았는데, spring.jpa.hibernate.ddl-auto 설정을 해줌으로 인해 서비스가 Bootstrap 되는 과정에서 내가 설계한 Entity 클래스의 내용을 바탕으로 자동으로 테이블을 생성해주는것을 알 수 있습니다.

또한 지금은 임의의 데이터를 생성해주지 않았음으로 결과 레코드는 조회가 되지 않는것이 정상입니다.

추가로 임의로 Entity에 필드를 추가하거나하는 등의 스키마 구조가 변경되는 경우 아래에서 확인할 수 있듯이 자동으로 데이터베이스 테이블 속성 변경이 이루어지는것도 알 수 있습니다.

Service 클래스 저장(Insert) 로직 작성

io.starter.jpatutorial.service.PostListService

@Service @RequiredArgsConstructor public class PostListService { private final PostMariaRepository postMariaRepository; @Transactional(readOnly = true) public List fetch() { return postMariaRepository.findAll() .stream() .map(Post::jpoOf) .collect(Collectors.toList()); } @Transactional public void save(List posts) { List postJpos = posts.stream() .map(Post::asJpo) .collect(Collectors.toList()); postMariaRepository.saveAll(postJpos); } }

Entity 저장(Insert) 로직 테스트

io.starter.jpatutorial.JpaTutorialApplicationTests

package io.starter.jpatutorial; import io.starter.jpatutorial.domain.model.Post; import io.starter.jpatutorial.service.PostListService; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest class JpaTutorialApplicationTests { @Autowired private PostListService postListService; @Test @DisplayName("게시글 목록 전체 조회 테스트") void fetch() { List posts = postListService.fetch(); Assertions.assertNotNull(posts); System.out.println("Posts --> " + posts); } @Test @DisplayName("게시글 생성 테스트") void save() { List posts = Arrays.asList( Post.builder().title("게시글 1").content("게시글 1 내용").build(), Post.builder().title("게시글 2").content("게시글 2 내용").build(), Post.builder().title("게시글 3").content("게시글 3 내용").build() ); postListService.save(posts); } }

임의의 임시 Post 객체들을 생성하여 save 로직을 수행하면 아래와 같이 insert SQL문이 수행되면서 데이터베이스에 정상적으로 생성됨을 확인할 수 있습니다.

프로젝트 전체 소스 코드는 아래 Github에서 참고 가능합니다.

Github Source Code

from http://antdev.tistory.com/88 by ccl(A) rewrite - 2021-12-12 16:02:03