study/Spring boot

스프링 부트 프로젝트 시작할 때, 몰랐던 것 - JPA(2)

올스왑 2021. 5. 18. 12:52

이동욱 님의 책 "스프링 부트와 AWS로 혼자 구현하는 웹 서비스"를 보면서 기본적인 것들, 팁들을 많이 알았다. 이글의 출처이다.

 

API를 만들기 위해 세 가지 클래스가 필요하다.
* Request 데이터를 받을 DTO
* API 요청을 받을 Controller
* 트랜잭션, 도메인 기능 간의 순서를 보장하는 Service
-> 여기서 Service는 트랜잭션, 도메인 간의 순서 보장의 역할만 한다.


스프링 웹 계층을 살펴보자

Web Layer : 뷰 템플릿의 영역. 컨트롤러, JSP 등. 더불어 외부 요청과 응답에 대한 전반적인 영역.

 

Service Layer : @Service에 사용되는 영역. 일반적으로 컨트롤러와 DAO의 중간 영역에서 사용된다.

-> @Transactional이 사영되어야 하는 영역(? 아직 이해가 잘 안 된다.)

 

Repository Layer : 데이터 저장소에 접근하는 영역. DAO 영역으로 이해하면 된다.(JpaRepostiory?)

 

DTOs : Data Transfer Object. 계층 간에 데이터 교환을 위한 객체들의 영역.

 

Domain Model : 도메인이라는 불리는 개발 대상을 모든 사람이 동일한 관점에서 이해할 수 있고 공유할 수 있도록 단순화시킨 것이다.(?) 택시 앱이라고 하면, 배차, 탑승, 요금 등이 모두 도메인이다.

* @Entity가 사용되는 영역도 도메인 모델이다.

* 무조건 데이터베이스의 테이블과 관계가 있어야만 하는 것은 아니다. -> VO처럼 값 객체들도 여기 해당되기 때문에

 

그렇다면 비즈니스 로직은 어디서 처리할까? Domain이다.

 

모든 로직이 서비스 클래스 내부에서 처리된다면, 서비스 계층이 무의미해지고, 객체는 단순히 데이터 덩어리 역할만 하게 된다.

하지만 도메인 모델에게서 처리할 경우, 서비스 메서드는 트랜잭션과 도메인 간의 순서만 보장해주면 된다.

(간단하게 말하면 전자는 세터 메서드로 값을 변경하고, 후자는 해당 객체가 갖는 메서드를 통해 값을 변경한다.

ex) order.setStatus("CANCEL")과 order.cancel()의 차이)


스프링에서 Bean을 주입받는 방식들은 여러 가지가 있다.

* @Autowired - 권장하지 않음.

* setter

* 생성자 - 생성자로 Bean 객체를 받는 방식. @RequiredArgsConstructor를 클래스에 붙여주면, final이 선언된 모든 필드를 인자 값으로 하는 생성자를 롬복의 @RequiredArgsConstructor가 생성해준다.

 

절대로 Entity 클래스는 Request, Response 클래스로 사용하지 않는다. -> Entity 클래스는 데이터베이스와 직접 연결되는 핵심 클래스이기 때문에.

* Entity 클래스를 기준으로 테이블이 생성된다. 하지만 request, response는 사소한 변경이 자주 일어나기 때문에 따로 만드는 것이 좋다. 또한 실제로 컨트롤러에서 결과값으로 여러 테이블을 조인해서 리턴하는 경우가 많으므로 Entity 클래스로만으로는 표현하기가 어려운 경우가 있다.

* 꼭 Entity 클래스와 컨트롤러에서 사용할 DTO는 분리해서 사용해야 한다.


API Controller를 테스트할 때는 @WebMvcTest를 사용하지 않는다. @WebMvcTest의 경우는 JPA 기능이 작동하지 않기 때문이다.(외부 연동과 관련된 부분만 활성화되기 때문에)
-> 따라서 JPA까지 한 번에 테스트할 경우에는 @SpringBootTest와 TestRestTemplate을 사용하면 된다.

 

* TestRestTemplate : restTemplate을 테스트에서 사용하기 위한 것이다. restTemplate은 http 통신을 위한 라이브러리이다.


JPA 영속성 컨텍스트
영속성 컨텍스트 : 엔티티를 영구 저장하는 환경.(논리적 개념)
-> JPA의 핵심 내용은 엔티티가 영속성 컨텍스트에 포함되어 있냐 아니냐로 갈린다.

JPA 엔티티 매니저가 활성화된 상태로(Spring Data Jpa의 기본값) 트랜잭션 안에서 데이터베이스에서 데이터를 가져오면 이 데이터는 영속성 컨텍스트가 유지된 상태이다.
이 상태에서 해당 데이터의 값을 변경하면 트랜잭션이 끝나는 시점에 해당 테이블에 변경분을 반영한다.
-> 엔티티 객체의 값만 변경하면 별도로 update 쿼리를 날릴 필요가 없다.
-> 이 개념을 더티 체킹(dirth checking)이라고 한다.

 


보통 엔티티에는 생성 시간, 수정 시간이 포함된다. 하지만 이것들에 대한 코드를 클래스마다 만들면 복잡해지고 지저분해진다. 그래서 이런 문제를 해결하기 위해 JPA Auditing을 사용한다.(자동으로 값을 넣어주는 기능)

 

이전에 알 것: 자바 8부터 LocalDate, LocalDateTime이 등장했다. 기존의 Date의 문제점을 고친 타입이다.
기존 Date와 Caledar의 문제점.
1. 불변 객체가 아님. 멀티스레드 환경에서 문제 발생 가능성 존재.
2. Calendar의 월 값 설계가 잘못됨.(10월을 나타내는 값의 숫자 값이 9 임)

엔티티들의 생성, 수정 시간을 위한 클래스를 따로 만들어 상속을 통해 관리한다.

시간 관리 클래스

@MappedSuperclass : JPA Entity 클래스들이 이 어노테이션이 붙은 클래스를 상속할 경우, 이 클래스의 필드들도 칼럼으로 인식되도록 한다.

@EntityListeners(AuditingEntityListener.class)
: 해당 클래스에 Auditing 기능을 포함시킨다.

@CreatedDate : entity가 생성되고 저장될 때 시간이 자동으로 저장됨.
@LastModifiedDate : 조회한 entity 값을 변경할 때 시간이 자동으로 저장됨.

JPA Auditing 어노테이션을 모두 활성화 하기 위해 메인 클래스에 활성화 어노테이션을 붙인다. -> @EnableJpaAuditing