순서
엔티티 구성(리포지토리)
→서비스 클래스
→컨트롤러
→API 테스트
application.yml
spring:
jpa:
# 전송 쿼리 확인
show-sql: true
properties:
hibernate:
format_sql: true
# 테이블 생성 후에 data.sql 실행
defer-datasource-initialization: true
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.2'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'org.example' // 지정한 그룹 이름
version = '1.0'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
package org.example.SpringBoot.domain;
import jakarta.persistence.*;
import lombok.Builder;
@Entity // 엔티티로 지정
public class Article {
@Id // 기본키 : id 필드
@GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키 자동으로 1씩 증가, MySQL의 AUTO_INCREMENT
@Column(name = "id", updatable = false) // 한번 저장된 후 수정 불가능 (영속화)
private Long id;
@Column(name = "title", nullable = false) // title은 not null로 지정
private String title;
@Column(name = "content", nullable = false) // name도 not null로 지정
private String content;
@Builder // 빌더 패턴으로 객체 생성
public Article(String title, String content) {
this.title = title;
this.content = content;
}
protected Article() { } // 기본 생성자
public Long getId() {
return id;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
}
@Builder
애너테이션은 롬복에서 지원하는 애너테이션으로,Builder
애너테이션을 생성자 위에 입력하면 빌더 패턴 방식으로 객체를 생성할 수 있어 편리합니다.빌더 패턴을 사용하면 객체를 유연하고 직관적으로 생성할 수 있어서 개발자들이 애용하는 디자인 패턴 입니다.
즉,빌더 패턴을 사용하면 어느 필드에 어떤 값이 들어가는지 명시적으로 파악할 수 있습니다.
빌더 패턴 예시
// 빌더 패턴 사용 x
new Article("abc", "def");
// 빌더 패턴 사용 o
Article.builder()
.title("abc")
.content("def")
.build();
어떤가요 ? 제가 볼때도 명시적으로 어떤 필드에 어떤 값이 들어가는지 파악이 쉽습니다! 빌더 패턴을 사용하지 않는 경우에는 어디에 "abc`가, 어디에 "def"가 들어가는지 파악하기 어렵지만, 빌더 패턴을 사용하니 title에 "abc"가, content에 "def"가 들어가는지 파악이 잘 되네요 : >
기존의
Article.java
코드를 수정해볼까요 ?
package org.example.SpringBoot.domain;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity // 엔티티로 지정
@Getter // Getter 메서드 대체
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {
@Id // 기본키 : id 필드
@GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키 자동으로 1씩 증가, MySQL의 AUTO_INCREMENT
@Column(name = "id", updatable = false) // 한번 저장된 후 수정 불가능 (영속화)
private Long id;
@Column(name = "title", nullable = false) // title은 not null로 지정
private String title;
@Column(name = "content", nullable = false) // name도 not null로 지정
private String content;
@Builder // 빌더 패턴으로 객체 생성
public Article(String title, String content) {
this.title = title;
this.content = content;
}
}
getId()
,getTitle()
의 게터 메서드들은@Getter
로 대치하고.
@NoArgsConstructor
애너테이션을 선언해 접근 제어자가 protected인 기본 생성자를 별도의 코드protected Article() { }
코드 없이 생성했습니다.이렇게 롬복의 애너테이션을 사용하면 가독성이 향상됩니다.
package org.example.SpringBoot.repository;
import org.example.SpringBoot.domain.Article;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BlogRepository extends JpaRepository<Article, Long> { }
JpaRepository 클래스를 상속받을 때 엔티티 Ariticle과 엔티티의 기본키(id)의 자료형 Long을 인수로 넣도록 코드를 작성합니다.
여기까지가 엔티티 구성입니다. : >
@AllArgsConstructor
은Lombok
의 어노테이션 중 하나로, 해당 클래스의 모든 필드에 대한 매개변수를 가진 생성자를 자동으로 생성합니다. 이 생성자는 클래스의 모든 멤버 변수를 매개변수로 받아서 인스턴스를 생성하는데 사용됩니다.
예를 들어, 다음과 같은 클래스가 있다고 가정해 봅시다:
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class Person {
private String name;
private int age;
}
@AllArgsConstructor
을 사용하면 다음과 같은 생성자가 자동으로 생성됩니다:
public Person(String name, int age) {
this.name = name;
this.age = age;
}
즉, 모든 필드에 대한 생성자를 별도로 작성하지 않고도 @AllArgsConstructor을 사용하여 간단하게 생성자를 생성할 수 있습니다. 이는 주로 불변(immutable)한 객체를 만들 때 또는 다양한 객체를 초기화해야 할 때 편리하게 사용됩니다.
앞으로 코드 작성하시면서 빨간색 오류는 일단은 넘어갈게요 !
PostMan과 TestCode 실행에는 영향이 없습니다 : >
AddArticleRequest.java
파일은DTO(Data Transfer Object)
로 계층끼리 데이터를 교환하기 위해 사용하는 객체입니다.
DAO
→ 데이터베이스와 연결되고 데이터를 조회하고 수정하는데 사용하는 객체로 데이터 수정과 관련된 로직이 포함
DTO
→ 단순하게 데이터를 옮기기 위해 사용하는 전달자 역할을 하는 객체, 별도의 비즈니스 로직을 포함하지 않음
package org.example.SpringBoot.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.example.SpringBoot.domain.Article;
@NoArgsConstructor // 기본 생성자 추가
@AllArgsConstructor // 모든 필드 값을 파라미터로 받는 생성자 추가
@Getter
public class AddArticleRequest {
private String title;
private String content;
public Article toEntity() { // 생성자를 사용해 객체 생성
return Article.builder()
.title(title)
.content(content)
.build();
}
}
이 코드에서 눈여겨 볼 부분은
toEntity()
는 빌더 패턴을 사용해 DTO를 엔티티로 만들어주는 부분입니다. 이 메서드는 추후 블로그 글을 추가할 떄 저장할 엔티티로 변환하는 용도입니다!
package org.example.SpringBoot.service;
import lombok.RequiredArgsConstructor;
import org.example.SpringBoot.domain.Article;
import org.example.SpringBoot.dto.AddArticleRequest;
import org.example.SpringBoot.repository.BlogRepository;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor // final이 붙거나 @NotNull이 붙은 필드의 생성자 추가
@Service // 빈으로 등록
public class BlogService {
private final BlogRepository blogRepository;
// 블로그 글 추가 메서드
public Article save(AddArticleRequest request) {
return blogRepository.save(request.toEntity());
}
}
@RequiredArgsConstructor
애너테이션은 롬복에서 지원하는 애너테이션입니다.빈으로 등록된
@Service
애너테이션은 해당 클래스를 서블릿 컨테이너로 등록합니다.
save()
메서드는JpaRepository
에서 지원하는 저장 메서드save()
로AddArticleRequest
클래스에 저장된 값들을article
데이터베이스에 저장합니다.
BlogRepository
는JpaRepository
를 상속받고
JpaRepository
는CrudRepository
를 상속받으며
CrudRepository
에save()
메서드가 선언되어 있습니다.
package org.example.SpringBoot.controller;
import lombok.RequiredArgsConstructor;
import org.example.SpringBoot.domain.Article;
import org.example.SpringBoot.dto.AddArticleRequest;
import org.example.SpringBoot.service.BlogService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
@RestController // HTTP Response Body에 객체 데이터를 JSON 형식으로 반환하는 컨트롤러
public class BlogApiController {
private final BlogService blogService;
// HTTP 메서드가 POST일 때, 전달 받은 URL와 동일하면 해당 메서드로 매핑
@PostMapping("/api/articles")
public ResponseEntity<Article> addArticle(@RequestBody AddArticleRequest request) {
Article savedArticle = blogService.save(request);
// 요청 자원이 성공적으로 생성 되었다면, 저장된 블로그 정보를 응답 객체에 담아 전송
return ResponseEntity.status(HttpStatus.CREATED)
.body(savedArticle);
}
}
http://localhost:8080/api/articles
은addArticle()
메서드에 매핑됩니다.
@ReqyestBody
애너테이션은 HTTP 요청을 할 때 응답에 해당하는 값을@RequestBody
애너테이션이 붙은 대상 객체인AddArticleRequest
에 매핑합니다.
@ResponseEntity.status().body()
는 응답코드 201로 테이블에 저장된 객체를 반환합니다.
HTTP 메서드
를POST
로 Postman을 이용해 보내보도록 하겠습니다.
controller
의 역할과 코드를 기억하시나요 ?
모든 요청의 처리는controller
에서 처리합니다.controller
패키지의BlogApiController.java
에서@PostMapping('api/articles")
코드에서POST
요청을 처리하는 과정을 작성했습니다 !
빨간색 박스의 영역을 입력하고 Send
를 누르게 되면
아래처럼 데이터가 삽입 됩니다.
이제 H2
를 이용해 확인해보겠습니다 !
H2
를 사용하려면resources
폴더application.yml
코드에H2
관련 코드를 넣어줘야 합니다 !
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password:
h2:
console:
enabled: true
path: /h2-console
settings:
web-allow-others: true
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
defer-datasource-initialization: true
서버 주소에
http://localhost:8080/h2-console
를 입력하시면 콘솔이 출력됩니다.application.yml
의datasource:
부분의driver-class-name
과url
,username
을 이용해 접속해봅시다 !
Test Conection
을 선택하면 테스트 성공 이라고 뜨게 됩니다.
이제 Connect
를 해보겠습니다 !
SQL 쿼리문
Select * from ARTICLE
이 보이시나요 ?
쿼리문을 작성하고Run
을 누르게 되면 Postman으로 넣은 값이 저장되어 있는걸 확인할 수 있습니다 !
개발하는 과정에서
Postman
과H2
를 사용해 매번 테스트하는 과정은 비효율적입니다. 따라서 이러한 과정들을 대신할 테스트 코드를 작성해보겠습니다.
BlogApiController
클래스에서 ALT + ENTER
를 누르면 Create Test
로 테스트 코드를 생성하겠습니다.
Given
: 블로그 글 추가에 필요한요청 객체를 생성
When
: 블로그 글 추가API에 요청
. 요청 타입은 JSON이며,Given
에서 미리 만들어둔 객체를 요청 본문으로 함께 전송
Then
:응답 코드가 201 Created인지 검증
.
Blog를 전체 조회해 크기가 1인지 확인, 실제로 저장된 데이터와 요청 값을 비교
블로그 테스트 코드
assertThat()
메서드 다른 테스트들은 아래에 표로 정리했습니다 : >
코드 설명 assertThat(articles.size()).isEqualTo(1);
블로그 글 크기가 1개여야 한다. assertThat(articles.size()).isGreaterThan(2);
블로그 글 크기가 2보다 커야한다. assertThat(articles.size()).isLessThan(5);
블로그 글 크기가 5보다 작아야한다. assertThat(articles.size()).isisZero();
블로그 글 크기가 0이어야 한다. assertThat(articles.title()).isEqualTo("제목");
블로그 글의 title 값은 "제목"이어야 한다. assertThat(articles.title()).isNotEmpty();
블로그 글의 title 값이 비어있지 않아야 한다. assertThat(articles.size()).contains("제");
블로그 글의 title 값이 "제"를 포함해야 한다.
HTTP에서는 JSON을 사용
하고,Java에서는 객체를 사용
합니다. 서로 다른 형식을 맞춰주기 위해 변환 작업을 제공하는 것이ObjectMapper
클래스입니다.
직렬화 : 자바 시스템 내부에서 사용되는 객체를 외부에서 사용하도록 데이터를 변환
역직렬화 : 직렬화의 반대로, 외부에서 사용하는 데이터를 자바의 객체 형태로 변환
하... h2 접속하실 때 404 not found 로 접속이 안되실텐데,,
여러 자료들을 찾아 봤는데,, 정말 간단히 해결되었습니다..
implementation 'org.springframework.boot:spring-boot-devtools'
이거 추가해주니 정상 작동 합니다....
추가적으로
JDBC URL
접속도 안됩니다,,
그래서 로그를 보시면2023-11-11T22:57:18.483+09:00 INFO 20340 --- [ restartedMain] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:75c836b2 ~'
이 로그에서 마지막 뒤의
jdbc:h2:mem:75c836b2 ~
이 부분들로 접속하시면 접속 됩니다 !!
아무래도 로그를 보고 이리저리 확인해본 결과
application.yml
파일이 적용이 안되는 것 같아 파일 구조를 잘 살펴보니,, 프로젝트를 생성할 때Build System
이Gradle
가 아닌IntelliJ
였나,, 프로젝트 생성 문제였습니다..
이 구조에서 resources의 노란색,,아이콘이 정상인데
그냥 패키지로 되어 있더라고요 ㅎㅎ...ㅎ...ㅎ......3시갆ㅎㅎㅎㅎ,....
implementation 'org.springframework.boot:spring-boot-devtools'
이거 안해도 됩니다 ㅎㅎ...ㅎ...yml
을 설정 안하셨다면 이전의 트러블 슈팅 방법으로 하시면 됩니다 ㅎㅎ...ㅎ...ㅎ...ㅎ....
resources
폴더의 노란색 아이콘은 일반적으로 Java 프로젝트 구조에서, 특히Maven 또는 Gradle을 사용할 때 특수 디렉터리
임을 나타냅니다. 이 폴더는 속성 파일, XML 구성 및 애플리케이션이 런타임에 필요로 하는 기타 비코드 리소스와 같은 리소스를 위해 지정됩니다. 이러한 리소스는 애플리케이션 내에서 쉽게 액세스할 수 있도록 클래스 경로에 포함되는 경우가 많습니다.