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<PostJpo, Long> {
}
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<Post> 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<Post> 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<Post> fetch() {
return postMariaRepository.findAll()
.stream()
.map(Post::jpoOf)
.collect(Collectors.toList());
}
@Transactional
public void save(List<Post> posts) {
List<PostJpo> 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<Post> posts = postListService.fetch();
Assertions.assertNotNull(posts);
System.out.println("Posts --> " + posts);
}
@Test
@DisplayName("게시글 생성 테스트")
void save() {
List<Post> 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
'Spring > Spring Data JPA' 카테고리의 다른 글
[Spring Data JPA] JPA Query Methods - QuickStart 5 (End) (0) | 2021.12.12 |
---|---|
[Spring Data JPA] Fetch Join (N + 1 Issue) - QuickStart 4 (0) | 2021.12.12 |
[Spring Data JPA] 연관 관계 자식(하위) Entity 작성 - QuickStart 3 (0) | 2021.12.12 |
[Spring Data JPA] 예제 프로젝트 생성 및 초기 환경 구성 - QuickStart 1 (0) | 2021.12.12 |
[Spring Data JPA] JPA(Java Persistence API), Hibernate, Spring Data JPA의 개요 (0) | 2021.12.12 |
댓글