어디까지 갈 수 있을까?
테스트 코드, JPA 본문
패키지 이름 소문자로
클래스, 인터페이스 이름 파스칼 표기법
변수명 카멜 표기법
1. 테스트 코드
TDD와 단위 테스트 차이
TDD(Test Driven Development) : 테스트 주도 개발, 테스트 코드를 먼저 작성하는 것
단위 테스트 : 기능 단위의 테스트 코드를 작성하는 것을 말함
단위 테스트를 하면 빠른 피드백을 얻을 수 있고 자동검증이 가능해
테스트 코드로 먼저 검증 후 그래도 못 믿겠을 때 프로젝트 실행해 확인하는 것 추천
Applicaiton 클래스
앞으로 만들 프로젝트의 메인클래스
@SpringBootApplication
스프링 Bean 읽기와 생성 가능하게 함, 해당 어노테이션이 있는 위치부터 읽기때문에 항상 프로젝트 최상단에 위치해야 한다.
SpringApplication.run
내장 WAS(톰캣)을 실행
문자열 출력 컨트롤러 생성
@RestController
JSON을 반환하는 컨트롤러 만들어줌
@ResponseBody를 메소드마다 자동으로 붙여준다고 생각
@GetMapping
Get 요청을 받을 수 있는 API 생성
문자열 출력 컨트롤러 테스트
테스트할 패키지를 만들어주고 테스트할 클래스이름 + Test를 붙여준다
@RunWith(SpringRunner.class)
스프링 부트 테스트와 JUnit 사이에 연결자 역할
@WebMvcTest
Controller Layer만 테스트 가능 하기 때문에 @SpringBootTest에 비해 가볍다
@Autowired
스프링이 관리하는 빈 주입
private MockMvc mvc
http get, post 메소드 등에 대한 테스트를 할 수 있다. mvc 목업을 만들어준다고 생각하자
mvc.perform(get("/hello"))
/hello 주소로 http get 요청을 한다
.andExpect(status().isOk())
mvc.perform의 결과를 검증한다
http header의 status를 검증한다. 여기서는 200인지 아닌지 검증
* 2xx 성공, 4xx 클라이언트 오류, 5xx 서버 오류
.andExpect(content().string(hello))
응답 본문의 내용이 hello가 맞는지 검증
롬복
implementation 'org.projectlombok:lombok'
testCompile "org.projectlombok:lombok"
annotationProcessor('org.projectlombok:lombok')
testAnnotationProcessor('org.projectlombok:lombok')
기본생성자, Getter, Setter, toString 자동 생성
문자열 출력 컨트롤러 롬복으로 전환하기
@Getter
선언된 모든 필드에 get 메소드 생성
@RequiredArgsConstructor
선언된 모든 final 필드가 포함된 생성자 생성
@AllArgsConstructor
모든 필드가 포함된 생성자 생성
@Getter, @RequiredArgsConstructor 제대로 동작하는지 테스트
assertThat
검증 메소드
isEqualTo
동등 비교 메소드
팁
처음에 Assertions 입력하고 assertj 선택한 다음 alt+enter로 Assertions를 static import 하면 편하게 사용 가능하다
롬복_기능_테스트를 하려고 하니 variable name not initialized in the default constructor 오류 발생.
위의 코드블럭에 적어놓은 대로 build.gradle에 적으면 오류가 안 난다.
롬복 기능 테스트 완료
파라미터 출력 컨트롤러
HelloController.java
@RequestParam
외부에서 API로 넘긴 파라미터를 가져오는 어노테이션
파라미터 출력 테스트
HelloControllerTest.java
jsonPath
JSON 응답값을 필드별로 검증
$를 기준으로 필드명을 명시
2. JPA
JPA는 자바 표준 ORM, 객체와 DB를 매핑
MyBtis, iBatis는 SQL Mapper, 쿼리와 DB를 매핑
JPA를 통해
객체지향적으로 DB에 접근할 수 있음(부모 자식 관계 표현, 상태·행위를 한 곳에서 관리)
SQL에 종속적인 개발을 하지 않아도 됨
Spring Data JPA
기본 CRUD(Save(), findAll(), findOne()...)을 인터페이스로 갖고 있어 인터페이스만으로 기본 기능 사용 가능
build.gradle 추가
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
요구사항 분석
게시글 저장 엔티티 생성
주요 어노테이션을 클래스에 가깝게 둠
@Entity
테이블과 매핑될 클래스 명시
@Id
PK필드
@GeneratedValue
PK 생성 규칙
@Column
테이블의 칼럼 명시
@NoArgsConstructor
기본 생성자 자동 추가
@Builder
해당 클래스의 빌더 패턴 클래스를 생성
어느 필드에 어떤 값을 채울지 명확하게 인지할 수 있다
@NoArgsContructor | 파라미터가 없는 생성자 생성 |
@RequiredArgsConstructor | final 필드, @NonNull 필드 생성자 생성 |
@AllArgsConstructor | 모든 필드에 대한 생성자 생성 |
Entity 클래스에서는 Setter 메소드를 만들지 않는다
값 삽입은 생성자를 통해 하고, 변경하고자 하는 레코드의 id값을 넣은 후 다른 값들을 넣어주면 update 문이 실행된다
게시글 저장 리포지토리 생성
JpaRepository가 DB접근을 가능하게 한다
인터페이스 생성 후 JpaRepository<Entity 클래스, PK타입>을 상속하면 기본 CRUD 메소드가 자동 생성
Entity 클래스와 Repository는 밀정한 관계이기 때문에 도메인 패키지에 함께 위치
@Repository를 추가할 필요가 없다
*기본 CRUD 메소드 : save, findAll, findById, findOne, deleteAll, deleteById, delete
게시글 저장 리포지토리 테스트
@After
단위 테스트가 끝날때마다 수행되는 메소드를 지정
postsRepository.save
id값이 있다면 update, 없다면 insert 쿼리가 실행
postsRepository.findAll
모든 데이터 조회
show_sql : 쿼리 로그 확인
MySQL5InnoDBDialect : 쿼리 로그를 MySQL 버전으로 변경
org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [insert into posts (author, content, title) values (?, ?, ?)]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement 오류 발생
spirngboot 버전만 2.1.9.RELEASE로 바꿔주고 빌드하면 해결된다
spring 웹 계층
Web Layer : 눈에 보이는 화면과 소통 / 컨트롤러, 뷰 템플릿, 필터, 인터셉터, 컨트롤러 어드바이스 등 외부 요청과 응답
Service Layer : 트랜잭션, 도메인 기능 간의 순서를 보장
Repository Layer : DB에 접근
Domain Model : @Entity, 비지니스 처리 담당
DTO : Request 데이터를 받음, 계층간 데이터 교환
게시글 등록/수정/조회 API 만들기
Request 데이터 받을 Dto
API 요청 받을 Controller
트랜잭션, 도메인 기능 간의 순서를 보장 Service 생성
스프링에서는 Bean을 주입받을 때 생성자로 주입받는 걸 권장함
@RequiredArgConstructor가 생성자 역할 해 줌
Entity 클래스와 거의 유사한 형태임에도 Dto 클래스를 추가로 생성한 이유:
Entity 클래스를 기준으로 테이블이 생성되고, 스키마가 변경되기 때문에 매우 중요한 클래스이다.
화면 변경과 같은 사소한 일이 일어났을 때 테이블과 연결된 Entity 클래스를 변경하는 것이 아니라 Dto 클래스를 이용한다.
Dto는 View를 위한 클래스라 자주 변경이 일어난다
View Layer와 DB Layer의 역할 분리 철저히 해주기
Entity : DB 접근
Dto : 뷰의 데이터 다른 계층에 넘겨주기
게시글 등록/수정/조회 API 테스트
@WebMvcTest는 JPA 기능이 작동하지 않기 때문에
JPA 기능까지 한 번에 테스트 할 때는 @SpringBootTest와 TestRestTemplate 사용
다시보기???
게시글 수정/조회 기능
PostsApiController.java
Dto는 Entity를 받아 처리함
RequestDto도 별도로 두는 이유는 여러테이블에 동시에 나눠 저장되거나,
일부는 Redis에 일부는 데이터베이스에 저장해야될 수 있기 때문
Posts.java
PostsService.java
update 기능에서 쿼리를 날리는 부분이 없는 이유
JPA의 영속성 컨텍스트 때문 *엔티티를 영구 저장하는 환경
JPA의 엔티티 매니저가 활성화된 상태로 트랜잭션 안에서 데이터를 가져오면 영속성 컨텍스트가 유지된 상태
이 상태에서 해당 데이터의 값을 변경하면 트랜잭션이 끝나는 시점에 해당 테이블에 변경분을 반영
즉, Entity 객체의 값만 변경하면 별도로 Update 쿼리를 날릴 필요가 없다
이 개념을 더티 채킹
게시글 수정/조회 기능 테스트
PostsApiControllerTest.java
application.properties에 h2 데이터베이스 웹 콘솔 옵션 활성화
JPA Auditing
엔티티에는 생성시간과 수정시간을 포함해 유지보수 용이하게 함
JPA Auditiing은 Spring Data JPA에서 시간에 대해서 자동으로 값을 넣어주는 기능
BaseTimeEntity는 모든 Entity의 상위 클래스가 되어 Entity들의 createdDate, modifiedDate를 자동으로 관리하는 역할
@MappedSuperclass
JPA Entity 클래스들이 BaseTimeEntity를 상속할 경우 필드들(createdDate, modifiedDate)도 칼럼으로 인식하도록 함
@EntityListeners(AuditingEntityListener.class)
BaseTimeEntity 클래스에 Auditing 기능을 포함
@CreatedDate
Entity가 생성되어 저장될 때 시간이 자동 저장
@LastModifiedDate
Entity의 값을 변경할 때 시간이 자동 저장
@EnableJpaAuditing
Jpa Auditing 어노테이션 모두 활성화
JPA Auditing 테스트
BaseTimeEntity를 상속받으면 등록일/수정일이 자동으로 해결된다
출처 : 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 (이동욱 저)
'책 > 스프링부트와 AWS로 혼자 구현하는 웹 서비스' 카테고리의 다른 글
EC2 서버에 프로젝트를 배포해보자 &코드가 푸시되면 자동으로 배포해 보자 (0) | 2021.04.12 |
---|---|
AWS 서버, 데이터베이스 환경을 만들어보자(EC2, RDS) (0) | 2021.04.08 |
스프링 시큐리티와 OAuth 2.0으로 로그인 기능 구현하기 (0) | 2021.03.25 |
[작성중] 프로젝트 구조 (0) | 2021.03.16 |
머스테치로 화면 구성하기 (0) | 2021.03.14 |