JPA 의 정의 및 간략한 소개는 다음 글에서 확인 할 수 있습니다.
https://chamggae.tistory.com/205
JPA 는 ORM 진영의 자바 명세서 (=인터페이스) 이고 이 JPA 인터페이스를 사용하기 위해 구현체가 필요합니다.
여기서 대표적으로 흔히 듣던 Hibernate 등이 그 구현체입니다.
하지만 본 글에서 JPA를 사용할 때에는 이 구현체들을 직접 다루지 않습니다.
구현체들을 더 쉽고 추상화시킨 것이 Spring Data JPA 입니다. (구현체 교체의 용이성, 저장소 교체의 용이성)
여기서 부터 실습 >>
Springboot 환경에서 Spring JPA 사용하고 테스트 해보기
- JPA Entity 를 사용하여 데이터 삽입 및 수정 조회
- JPA Audit 소개
환경
- SpringBoot 2.7.6
- junit 4.13.2
- Spring JPA
- h2 (인메모리 DB)
- lombok
- gradle / application.yml
build.gradle 정보
buildscript {
ext {
springBootVeresion = '2.7.6.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVeresion}")
}
}
plugins {
id 'java'
id 'eclipse'
id 'org.springframework.boot' version '2.7.6'
id 'io.spring.dependency-management' version '1.1.0'
}
group 'org.example'
version '1.0-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'junit:junit:4.13.2'
implementation('org.springframework.boot:spring-boot-starter-data-jpa') // SpringBoot 용 Spring Data Jpa 라이브러리, Spring에서 Hibernate라는 구현체를 직접 사용하지 않는다고 한다. ,
implementation('com.h2database:h2') // 인메모리 관계형 데이터베이스, 테스트 용도로 많이 사용
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}
tasks.named('test') {
useJUnitPlatform()
}
1. Entity 생성
Posts 엔티티
import com.example.springbootAWS.domain.BaseTimeEntity;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
@Getter
@NoArgsConstructor
@Entity // Entity에서는 Setter가 아닌, 명확한 Function사용한다
public class Posts extends BaseTimeEntity {
@Id // PK 필드
@GeneratedValue(strategy = GenerationType.IDENTITY) // auto increment
private Long id;
@Column(length = 500, nullable = false) // default = varchar(255)
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;
}
public void update(String title, String content) {
this.title = title;
this.content = content;
}
}
Post 생성 및 업데이트 기능을 추가한 엔티티 입니다.
JPA Audit 이란 ?
보통 데이터 작업을 할 때 생성 시간 및 업데이트 날짜는 중요한 정보이기에 같이 저장하는 경우가 있습니다.
다만 이런 반복적인 코드는 JPA Auditing 기능으로 해결할 수 있습니다.
Date 뿐만아니라 @CreatedBy, @ModifiedBy 와 같은 어노테이션도 제공하고 있습니다.
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
// 단순 반복적인 audit 코드를 해결할 수 있다
@Getter
@MappedSuperclass // JPA Entity 클래스들이 BaseTimeEntity를 상속할 경우 Date 필드들도 칼럼으로 인식하도록 한다
@EntityListeners(AuditingEntityListener.class) // JPA Auditing
public abstract class BaseTimeEntity {
@CreatedDate // Entity 생성 시간
private LocalDateTime createdDate;
@LastModifiedDate // Entity 변경 시간
private LocalDateTime modifiedDate;
}
단 JPA Auditing 을 사용하기 위해 Application 상단에 @EnableJpaAuditing 어노테이션이 필요합니다.
@SpringBootApplication
@EnableAsync
@EnableJpaAuditing // JPA Auditing 활성화
@Slf4j
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. Repository 생성
PostsRepository
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostsRepository extends JpaRepository<Posts, Long> { // <Entity 클래스, PK 타입>
}
DB Layer 접근자 입니다. JPA 에서는 Repository라는 명칭을 쓰며 인터페이스로 사용합니다.
JpaRepository를 상속하면 기본적으로 CRUD 메소드를 사용할 수 있습니다.
3. Service 정보
import com.example.springbootAWS.domain.posts.Posts;
import com.example.springbootAWS.domain.posts.PostsRepository;
import com.example.springbootAWS.web.dto.PostsResponseDto;
import com.example.springbootAWS.web.dto.PostsSaveRequestDto;
import com.example.springbootAWS.web.dto.PostsUpdateRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
@Service
public class PostsService {
private final PostsRepository postsRepository;
@Transactional
public Long save(PostsSaveRequestDto requestDto) {
return postsRepository.save(requestDto.toEntity()).getId();
}
/*
쿼리를 날리지 않고 가능한 이유는 JPA의 영속성 컨텍스트 때문이다.
영속성 컨텍스트 : 엔티티를 영구 저장하는 환경 *더티 체킹
*/
@Transactional
public Long update(Long id, PostsUpdateRequestDto requestDto) {
Posts posts = postsRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id= "+id));
posts.update(requestDto.getTitle(), requestDto.getContent());
return id;
}
public PostsResponseDto findById(Long id) {
Posts entity = postsRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id= "+id));
return new PostsResponseDto(entity);
}
}
*더티 체킹 : 더티 체킹이란 JPA 측에서 엔티티의 변화가 있는 대상을 모두 update 처리하는 기능
변화의 기준은 최초의 엔티티 상태.
JpaRepository 테스트 하기 >
import org.junit.After;
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 java.time.LocalDateTime;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
@RunWith(SpringRunner.class)
@SpringBootTest
public class PostsRepositoryTest {
@Autowired
PostsRepository postsRepository;
@After
public void cleanup() {
postsRepository.deleteAll();
}
@Test
public void 게시글저장_불러오기() {
// given
String title = "게시글";
String content = "본문";
postsRepository.save(Posts.builder()
.title(title)
.content(content)
.author("test@gmail.com")
.build());
//when
List<Posts> postsList = postsRepository.findAll();
//then
Posts posts = postsList.get(0);
assertThat(posts.getTitle()).isEqualTo(title);
assertThat(posts.getContent()).isEqualTo(content);
}
@Test
public void BaseTimeEntity_등록() {
//given
LocalDateTime now = LocalDateTime.of(2023, 8, 4, 0, 0, 0 );
postsRepository.save(Posts.builder()
.title("title")
.content("content")
.author("author")
.build());
//when
List<Posts> postsList = postsRepository.findAll();
//then
Posts posts = postsList.get(0);
System.out.println(">>>>>> createDate = "+posts.getCreatedDate() + ", modifiedDate = "+posts.getModifiedDate());
assertThat(posts.getCreatedDate()).isAfter(now);
assertThat(posts.getModifiedDate()).isAfter(now);
}
}
'Java > Spring' 카테고리의 다른 글
[SpringBoot] 스프링 시큐리티 OAuth2 구글 로그인 API 연동하기 (0) | 2023.08.17 |
---|---|
[Spring Boot] 스프링 부트 mustache 한글 깨짐 현상 (0) | 2023.08.08 |
[Spring] JPA 란 무엇인가 (정의, 장단점) (0) | 2023.06.01 |
Springboot 환경에서 @Async와 ThreadPoolTaskExecutor (0) | 2023.05.26 |
[SpringBoot] SpringBoot 에 RabbitMQ 연동하기 (0) | 2022.08.05 |