[Web Service Project] 3. SpringBoot & JPA로 간단 API 만들기(2)
Project Structure
API를 만들기 위한 클래스
- Request 요청을 받는 Dto
- API 요청을 받는 Controller
- 트랜잭션, 도메인 간의 순서를 보장하는 Service
Spring 웹 계층
Web Layer
- 컨트롤러, 뷰 템플릿
- 외부 요청과 응답에 대한 전반적인 영역
Service Layer
- @Service
- 컨트롤러와 Dao의 중간 영역
- Transactionl 사용 영역
Repository Layer
- DB에 접근하는 영역
Dtos
- 계층 간 데이터를 교환하기 위한 객체들의 영역
Domain Model
- @Entity, VO
비즈니스 처리를 담당하는 곳 --> Domain
테스트
@WebMvcTest -> JPA 기능이 작동하지 않는다.
109p 코드를 그대로 쓰면 오류가 난다. -> 해결링크
영속성 컨테스트
엔티티를 영구 저장하는 환경
JPA의 엔티티 매니저가 활성화된 상태(default)로 트랜잭션 안에서 데이터베이스에서 데이터를 가져오면 영속성 컨텍스트가 유지된 상태
-> 트랜잭션이 끝나는 시점에 해당 테이블에 변경분을 반영
-> 별도로 update 쿼리 날릴 필요 X (더티 체킹)
서비스 위에 @Transactional이 있으면 바뀌나보다 ... !!
3.5 JPA Auditing으로 생성시간/수정시간 자동화하기
BaseTimeEntity를 생성한 걸로 만들 때마다 CreateDate와 ModifiedDate를 계속 넣지 않아도 되어서 굉장히 편해졌다.
Postman + 웹 콘솔로 검증
별도로 입력화면이 없기 때문에 Postman을 통해 Post로 데이터를 전송하여 검증하겠습니다.
Local 환경에선 DB로 H2 를 사용하기 때문에 SpringBoot에서 H2를 활성화시키도록 옵션을 추가하겠습니다.
src/main/resources 에 있는 application.properties를 application.yml로 변경후 아래와 같이 코드를 입력합니다.
spring: h2: console: enabled: true
Tip)
application.properties를 사용하셔도 무방합니다만, 굳이 yml로 변경한 이유는 properties에 비해 상대적으로 유연한 구조를 가졌기 때문입니다.
yml은 상위 계층에 대한 표현, List 등을 완전하게 표현할수가 있습니다.
최근의 많은 도구들이 yml 설정을 지원하기 때문에 이참에 시작해보시는 것을 추천드립니다.
application.yml 설정이 끝났다면 다시 Application.java를 실행시키고, 브라우저에서 http://localhost:8080/h2-console를 입력합니다.
이렇게 H2 DB를 관리할 수 있는 웹 클라이언트에 접속할 수 있습니다.
connect 버튼을 클릭하여 접속하신후, 조회 쿼리를 실행합니다.
SELECT * FROM POSTS;
현재 전혀 데이터가 없는 것이 확인 되었습니다.
자 그럼 포스트맨을 통해서 데이터를 전송해보겠습니다.
{ "title": "테스트 타이틀", "content": "테스트 본문", "author": "테스터" }
Send버튼을 클릭하시고 다시 localhost:8080/h2-console을 확인해보시면!
DB에 정상적으로 데이터가 입력된 것을 확인할 수 있습니다!
테스트 코드가 통과되니 실제로 어플리케이션을 실행시켜도 결과가 똑같음을 확인할 수 있었습니다.
이제 좀더 테스트 코드를 신뢰할수 있겠죠?
Tip)
만약 IntelliJ를 사용하신다면 Postman을 대체할 수 있는 .http 를 사용하시는걸 추천드립니다.
자세한 내용은 IntelliJ의 .http로 Postman 대체하기 를 참고하시면 좋습니다!
2-5. 생성시간/수정시간 자동화 - JPA Auditing
보통 Entity에는 해당 데이터의 생성시간과 수정시간을 포함시킵니다.
언제 만들어졌는지, 언제 수정되었는지 등은 차후 유지보수에 있어 굉장히 중요한 정보이기 때문입니다.
그렇다보니 매번 DB에 insert하기전, update 하기전에 날짜 데이터를 등록/수정 하는 코드가 여기저기 들어가게 됩니다.
// 생성일 추가 코드 예제 public void savePosts(){ ... posts.setCreateDate(new LocalDate()); postsRepository.save(posts); ... }
이런 단순하고 반복적인 코드가 모든 테이블과 서비스 메소드에 포함되어야 한다고 생각하면 어마어마하게 귀찮고 코드가 더러워지겠죠?
그래서 이 문제를 해결하기 위해 JPA Auditing를 사용하겠습니다.
LocalDate 사용
여기서부터는 날짜 타입을 사용합니다.
Java8 부터 LocalDate와 LocalDateTime이 등장했는데요.
그간 Java의 기본 날짜 타입인 Date의 문제점을 제대로 고친 타입이라 Java8일 경우 무조건 써야한다고 생각하시면 됩니다.
Tip)
Java8이 나오기전까진 JodaTime이라는 오픈소스를 사용해서 문제점들을 피했었고, Java8에선 LocalDate를 통해 해결했습니다.
자세한 내용은 Naver D2 - Java의 날짜와 시간 API 를 참고하시면 좋습니다!
현재 SpringDataJpa 버전에선 LocalDate와 LocalDateTime이 Database 저장시 제대로 전환이 안되는 이슈가 있습니다.
이 문제를 SpringDataJpa의 코어 모듈인 Hibernate core 5.2.10부터는 해결되었어서 이 부분을 교체해보겠습니다.
build.gradle 파일에서 2줄의 코드를 추가합니다.
buildscript { ... dependencies { ... //추가 classpath "io.spring.gradle:dependency-management-plugin:1.0.4.RELEASE" } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' .... //Spring Boot Overriding ext['hibernate.version'] = '5.2.11.Final' //추가 dependencies { ... }
SpringDataJpa가 사용하는 Hibernate의 버전만 5.2.11로 변경해서 사용하겠다는 의존성 변경 코드입니다.
이렇게 하면 LocalDate/LocalDateTime과 Database간 문제는 해결됩니다!
이제 본격적으로 공용 생성일/수정일 코드를 작성하겠습니다.
BaseTimeEntity 생성
src/main/java/com/jojoldu/webservice/domain에 아래와 같이 BaseTimeEntity 클래스를 생성하겠습니다.
@Getter @MappedSuperclass @EntityListeners(AuditingEntityListener.class) public abstract class BaseTimeEntity { @CreatedDate private LocalDateTime createdDate; @LastModifiedDate private LocalDateTime modifiedDate; }
BaseTimeEntity클래스는 모든 Entity들의 상위 클래스가 되어 Entity들의 createdDate, modifiedDate를 자동으로 관리하는 역할입니다.
- @MappedSuperclass
- JPA Entity 클래스들이 BaseTimeEntity을 상속할 경우 필드들(createdDate, modifiedDate)도 컬럼으로 인식하도록 합니다.
- @EntityListeners(AuditingEntityListener.class)
- BaseTimeEntity클래스에 Auditing 기능을 포함시킵니다.
- @CreatedDate
- Entity가 생성되어 저장될 때 시간이 자동 저장됩니다.
- @LastModifiedDate
- 조회한 Entity의 값을 변경할 때 시간이 자동 저장됩니다.
그리고 Posts클래스가 BaseTimeEntity를 상속받도록 변경합니다.
... public class Posts extends BaseTimeEntity { ... }
마지막으로 JPA Auditing 어노테이션들을 모두 활성화 시킬수 있도록 Application 클래스에 활성화 어노테이션 하나를 추가하겠습니다.
@EnableJpaAuditing // JPA Auditing 활성화 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
자 그러면 실제 코드는 완성이 되었습니다.
기능이 잘 작동하는지 테스트 코드를 작성해보겠습니다.
JPA Auditing 테스트 코드 작성하기
PostsRepositoryTest 클래스에 테스트 메소드를 하나더 추가하겠습니다.
@RunWith(SpringRunner.class) @SpringBootTest public class PostsRepositoryTest { @Autowired PostsRepository postsRepository; @After public void cleanup() { postsRepository.deleteAll(); } @Test public void 게시글저장_불러오기() { ... } @Test public void BaseTimeEntity_등록 () { //given LocalDateTime now = LocalDateTime.now(); postsRepository.save(Posts.builder() .title("테스트 게시글") .content("테스트 본문") .author("jojoldu@gmail.com") .build()); //when List<Posts> postsList = postsRepository.findAll(); //then Posts posts = postsList.get(0); assertTrue(posts.getCreatedDate().isAfter(now)); assertTrue(posts.getModifiedDate().isAfter(now)); } }
Posts를 저장한뒤, 해당 Posts에 createdDate와 modifiedDate이 있는지 확인해보는 코드입니다.
이 테스트 코드를 실행시켜보시면!
테스트코드가 성공적으로 통과했습니다!
마지막으로 실제 DB에도 날짜 데이터가 잘 들어갔는지 확인해보겠습니다.
Application 클래스를 다시 실행시키신뒤 PostMan으로 데이터를 전송하겠습니다.
그리고 h2 console에서 다시 데이터를 확인해보시면!
기존에 없던 created_date 와 modified_date가 값을 가진채로 추가된것을 확인할 수 있습니다!
앞으로 추가될 Entity들은 더이상 등록일/수정일로 고민할 필요가 없겠죠?
BaseTimeEntity만 상속받으면 자동으로 해결되기 때문입니다.
SpringBoot를 해보신 분들이라면 쉬우셨을텐데 처음이신분들에겐 다소 장황하게 진행되었을것 같기도해서 걱정입니다.
그래도 실제로 SpringBoot & JPA를 쓰는 회사에서 사용하는 방법들이니 직접 해보시면서 익혀보셨으면 합니다.
다음 시간엔 View Template을 사용하여 화면을 만들어보겠습니다.
긴글 끝까지 읽어주셔서 감사합니다^^