2020/05/18 에 네이버 블로그에서 작성한 내용 이사 🚗
🚩 JPA 소개
현대 웹 애플리케이션에서는 대부분 관계형 DB를 사용하니 객체를 관계형 DB에서 관리하는 것은 중요하다.
어떻게 데이터를 저장할지에만 초점
기능과 속성을 한 곳에서 관리하는 기술
JPA? 자바 표준명세서 (인터페이스)
인터페이스인 JPA를 사용위해서는 구현체가 필요한데 Spring에서는 Hibernate가 아닌 Spring Data JPA 모듈 사용해보자!
Spring Data JPA
spring-boot-starter-data-jpa
package com.org.example.springboot.domain.posts;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Getter
@NoArgsConstructor
@Entity
public class Posts {
@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 Posts(String title, String content,String author){
this.title=title;
this.content=content;
this.author=author;
}
}
롬복의 어노테이션@들은 코드 변경량을 최소화 시켜준다.
Entity 클래스에서는 절대 Setter 메소드를 만들지 않는다.
그럼 어떻게 값을 채워 DB에 삽입하냐?
기본적으로는 생성자 통해, 값 변경 필요하면 해당 이벤트에 맞는 public 메소드로
여기서는 생성자 대신 @Builder를 통해 제공되는 빌더 클래스 사용할거임
왜냐면 빌더 사용하면 어느 필드에 어떤 값 채워야할지 명확히 인지 가능!
JPA에서는 Repository라고 부름. 인터페이스로 생성함
인터페이스 생성 후 JpaRepository<Entity 클래스, PK 타입> 상속하면 기본적 CRUD 메소드 자동으로 생성됨
Entity 클래스와 기본 Entity Repository는 함께 위치해야 함!
Entity 클래스는 기본 레파지토리 없이 제대로 역할 못함
그래서 이렇게 domain패키지 안에 함께^^
package com.org.example.springboot.web.domain.posts;
import com.org.example.springboot.domain.posts.Posts;
import com.org.example.springboot.domain.posts.PostsRepository;
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.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@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("wlgp2500@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);
}
}
spring.jpa.show_sql=true
dialect.MySQL5InnoDBDialect
그럼 이제 테스트할 때 콘솔에 쿼리 로그 나옴~
중간에 이 책의 깃허브가서 이슈 봤는데 그레이들 버전때문에 오류났다는 글보고
나도 후다닥 gradle 4.10.2버전으로 다운그레이드 함
터미널에다가 밑에 코드 입력하면 된다.
gradlew wrapper --gradle-version 4.10.2
이 세가지 클래스가 필요함!
Web Layer
Service Layer
Repository Layer
Dtos
Domain Model
여기서 Entity 클래스인 Posts 클래스를 또 정의한 이유는
Entity 클래스를 Request/Response 클래스로 사용하면 안되서!!!
Entity 클래스는 DB와 맞닿아 테이블 생성하고 스키마 변경되고 그러는데 화면 변경같은 빈번한 변경을
테이블과 연결된 Entity 클래스를 변경하는 건 너무 큰 변경이다.
Entity 클래스와 Controller에서 쓸 Dto는 분리해서 사용하기^^
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private PostsRepository postsRepository;
@After
public void tearDown() throws Exception{
postsRepository.deleteAll();
}
@Test
public void Posts_등록된다() throws Exception{
//given
String title="title";
String content="content";
PostsSaveRequestDto requestDto=PostsSaveRequestDto.builder()
.title(title)
.content(content)
.author("author")
.build();
String url="http://localhost:"+port+"/api/v1/posts";
//when
ResponseEntity<Long> responseEntity=restTemplate.postForEntity(url,requestDto,Long.class);
//then
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<Posts> all=postsRepository.findAll();
assertThat(all.get(0).getTitle()).isEqualTo(title);
assertThat(all.get(0).getContent()).isEqualTo(content);
}
}
update에는 쿼리를 날리는 부분이 없는데 JPA의 영속성 컨텍스트 때문이다.
JPA의 영속성 컨텍스트? 엔티티를 영구 저장하는 환경
트랜잭션 안에서 DB에서 데이터 가져오면 이 데이터는 영속성 컨텍스트가 된다.
이 상태에서 해당 데이터 값 변경하면 트랜잭션 끝나는 시점에 해당 테이블에 변경분 반영. = 더티체크
엔팉티에는 해당 데이터 생성 / 수정 시간을 포함한다.
매번 DB 삽입하기 전 갱신하는 코드 작성하기 귀찮으니 JPA Auditing을 사용하자! (LocalDate 사용)
모든 Entity의 상위 클래스가 되어 Entity들의 생성 수정 날짜 관리
@Test
public void BaseTimeEntity_등록(){
//given
LocalDateTime now= LocalDateTime.of(2020,5,20,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);
}
3번째 글도 이사 완료 ^~^