1️⃣ Entity 생성
- DB와 매핑하기 위해서 Entity 계층을 만들어주고 필요한 필드들을 선언
💚 PostEntity
package com.gdsc_teamb.servertoyproject.domain.post.domain;
import com.gdsc_teamb.servertoyproject.domain.user.domain.UserEntity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Table(name = "Post")
@NoArgsConstructor
@Getter
public class PostEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@NotNull
private UserEntity user;
@Size(max = 50)
@NotNull
private String title;
@NotNull
private String content;
@CreationTimestamp
private LocalDateTime created_at;
@UpdateTimestamp
private LocalDateTime updated_at;
@Builder
public PostEntity(UserEntity user, String title, String content) {
this.user = user;
this.title = title;
this.content = content;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof PostEntity postEntity)) return false;
if (this.id == null || postEntity.getId() == null) return false;
return Objects.equals(this.id, postEntity.getId()) &&
Objects.equals(this.user, postEntity.getUser()) &&
Objects.equals(this.title, postEntity.getTitle()) &&
Objects.equals(this.content, postEntity.getContent()) &&
Objects.equals(this.created_at, postEntity.getCreated_at());
}
@Override
public int hashCode() {
return Objects.hash(id, user, created_at);
}
}
2️⃣ Repository 생성
💚 PostRepository
package com.gdsc_teamb.servertoyproject.domain.repository;
import com.gdsc_teamb.servertoyproject.domain.post.domain.PostEntity;
import com.gdsc_teamb.servertoyproject.domain.user.domain.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
// PostEntity 관리하는 JPA 레포
// PostEntity 에 대한 CRUD 작업 제공
@Repository
public interface PostRepository extends JpaRepository<PostEntity, Long> {
//ID 기준으로 내림차순되게 쿼리 정의함
//@Query("SELECT p FROM PostEntity p ORDER BY p.id DESC")
List<PostEntity> findAllByOrderByIdDesc();
List<PostEntity> findAllByUser(UserEntity user);
}
3️⃣ RequestDto 생성
💚 BoardDto
- 게시글 제목이 공백이거나 . 50글자를 초과하지 못하도록 함
package com.gdsc_teamb.servertoyproject.dto.boardDto;
import com.gdsc_teamb.servertoyproject.domain.post.domain.PostEntity;
import com.gdsc_teamb.servertoyproject.domain.user.domain.UserEntity;
import lombok.*;
import org.apache.catalina.User;
import java.time.LocalDateTime;
// 게시글의 생성 및 수정에 사용도는 데이터 전송
@Getter
@Setter
@ToString
@NoArgsConstructor
public class BoardDto {
private String title; // 게시글 제목
private String content; // 게시글 내용
private String nickname; // 작성자 이름
//BoardDto 생성자
@Builder
public BoardDto(String title, String content, UserEntity user){
this.title=title;
this.content=content;
this.nickname=user.getNickname();
}
// BoardDto 를 PostEntity 로 변환하는 메서드
// 변환된 PostEntity 가 return
public PostEntity toEntity(UserEntity user){
PostEntity build = PostEntity.builder()
.user(user)
.title(title)
.content(content)
.build();
return build;
}
// 게시글 제목 관련 설정 메서드
// 제목이 공백이거나 50자 이상을 초과할 경우 IllegalArgumentException 발생됨
public void setTitle(String title) {
if (title == null || title.trim().isEmpty()) {
throw new IllegalArgumentException("게시판 제목은 공백일 수 없습니다.");
}
if (title.length() > 50) {
throw new IllegalArgumentException("게시판 제목은 " + 50 + "자 이하로 입력해야 합니다.");
}
this.title = title;
}
// 게시글 내용 관련 설정 메서드
// 내용이 공백일 경우 IllegalArgumentException 발생됨
public void setContent(String content) {
if (content == null || content.trim().isEmpty()) {
throw new IllegalArgumentException("게시판 내용은 공백일 수 없습니다.");
}
this.content = content;
}
}
💚BoardUpdateDto
package com.gdsc_teamb.servertoyproject.dto.boardDto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
//게시글 수정 시 사용
//수정 할 게시글의 제목과 내용 담음
@Getter
@NoArgsConstructor
public class BoardUpdateDto {
private String title; // 수정할 게시글 제목
private String content; // 수정할 게시글 내용
//BoardUpdateDto 의 생성자
@Builder
public BoardUpdateDto(String title, String content){
this.title=title;
this.content=content;
}
}
4️⃣ Service 생성
💚 BoardService
package com.gdsc_teamb.servertoyproject.service;
import com.gdsc_teamb.servertoyproject.domain.post.domain.PostEntity;
import com.gdsc_teamb.servertoyproject.domain.repository.PostRepository;
import com.gdsc_teamb.servertoyproject.domain.user.domain.UserEntity;
import com.gdsc_teamb.servertoyproject.domain.user.domain.UserRepository;
import com.gdsc_teamb.servertoyproject.dto.boardDto.BoardDto;
import com.gdsc_teamb.servertoyproject.dto.boardDto.BoardListDto;
import com.gdsc_teamb.servertoyproject.dto.boardDto.BoardReadDto;
import com.gdsc_teamb.servertoyproject.dto.boardDto.BoardUpdateDto;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class BoardService {
private final PostRepository postRepository;
private final UserRepository userRepository;
@Transactional
public ResponseEntity<Object> savePost(BoardDto boardDto) {
try{
UserEntity user=userRepository.findByNickname(boardDto.getNickname()).
orElseThrow(()->new IllegalArgumentException());
// PostEntity 생성
PostEntity postEntity = boardDto.toEntity(user);
postRepository.save(postEntity); // 저장
return ResponseEntity.ok(boardDto.toEntity(user).getId());
}catch(Exception e){
return ResponseEntity.badRequest().body(e.getMessage());
}
}
@Transactional
public ResponseEntity<Object> updatePost(Long id, BoardUpdateDto boardUpdateDto) {
try {
PostEntity post = postRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException());
post.update(boardUpdateDto.getTitle(), boardUpdateDto.getContent());
return ResponseEntity.ok("게시글이 성공적으로 수정되었습니다. id= "+id);
} catch (Exception e) {
return ResponseEntity.badRequest().body("게시글 수정에 실패했습니다. " + e.getMessage());
}
}
}
📌 savePost()
- 게시글 작성 메서드
- boardDto 는 작성할 게시글의 정보를 담음
- 작성된 게시글의 id 를 return
📌 updatePost()
- 게시글 수정 메서드
- id는 수정할 게시글의 id를 담고, boardUpdateDto 는 수정할 내용을 담음
- 수정된 게시글의 id 를 return
5️⃣ Controller 생성
💚 BoardController
package com.gdsc_teamb.servertoyproject.controller;
import com.gdsc_teamb.servertoyproject.dto.boardDto.BoardDto;
import com.gdsc_teamb.servertoyproject.dto.boardDto.BoardListDto;
import com.gdsc_teamb.servertoyproject.dto.boardDto.BoardReadDto;
import com.gdsc_teamb.servertoyproject.dto.boardDto.BoardUpdateDto;
import com.gdsc_teamb.servertoyproject.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
// HTTP POST 요청을 통해 새로운 게시글을 생성
// 매개변수: 새로운 게시글을 생성하기 위한 데이터를 담은 DTO
// @RequestBody 어노테이션은 JSON 데이터를 BoardDto 객체로 변환
// 반환 값: 새로 생성된 게시글의 ID를 반환함
// 반환된 ID는 클라이언트에게 제공 (응답으로 전송) -> 클라이언트가 나중에 수정, 삭제할때 사용
@PostMapping("/api/boards")
public ResponseEntity<Object> save(@RequestBody BoardDto boardDto){
return boardService.save(boardDto);
}
// HTTP PUT 요청을 통해 특정 ID의 게시글을 수정
// 매개변수: 수정할 게시글의 ID, 수정할 내용이 담긴 DTO
// @PathVariable 을 통해 경로에서 추출된 게시글의 ID를 전달 받고, @RequestBody를 통해 클라이언트로부터 전달된 JSON 데이터를 변환하여 requestDto로 전달
// 반환 값: 수정이 완료된 게시글의 ID를 반환
@PutMapping("/api/boards/{id}")
public ResponseEntity<Object> update(@PathVariable Long id, @RequestBody BoardUpdateDto requestDto){
return boardService.update(id, requestDto);
}
}
📌 save()
- HTTP POST 요청을 통해 새로운 게시글을 생성
- 매개변수: 새로운 게시글을 생성하기 위한 데이터를 담은 DTO
- @RequestBody 어노테이션은 JSON 데이터를 BoardDto 객체로 변환
- 반환 값: 새로 생성된 게시글의 ID를 반환함
- 반환된 ID는 클라이언트에게 제공 (응답으로 전송) -> 클라이언트가 나중에 수정, 삭제할때 사용
📌 update()
- HTTP PUT 요청을 통해 특정 ID의 게시글을 수정
- 매개변수: 수정할 게시글의 ID, 수정할 내용이 담긴 DTO
- @PathVariable 을 통해 경로에서 추출된 게시글의 ID를 전달 받고, @RequestBody를 통해 클라이언트로부터 전달된 JSON 데이터를 변환하여 requestDto로 전달
- 반환 값: 수정이 완료된 게시글의 ID를 반환
6️⃣ TestCode
💚 PostRepositoryTest
- 게시글이 저장되는지, 잘 불러와지는지에 대한 테스트 코드
package com.gdsc_teamb.servertoyproject.domain.repository;
import com.gdsc_teamb.servertoyproject.domain.post.domain.PostEntity;
import com.gdsc_teamb.servertoyproject.domain.user.domain.UserEntity;
import com.gdsc_teamb.servertoyproject.domain.user.domain.UserRepository;
import jakarta.transaction.Transactional;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@SpringBootTest
@Transactional
class PostRepositoryTest {
@Autowired
private PostRepository postRepository;
@Autowired
private UserRepository userRepository;
@Test
@DisplayName("게시글 저장 & 불러오기")
public void savePost_Load(){
final String TITLE = "title-test";
final String CONTENT = "content-test";
//Given
UserEntity savedUser = userRepository.save(UserEntity.builder()
.email("abc@abc.com")
.password("password1234")
.nickname("nickname")
.phone("01012345678")
.build());
PostEntity savedPost = postRepository.save(PostEntity.builder()
.title(TITLE)
.content(CONTENT)
.user(savedUser)
.build());
// When
List<PostEntity> postsList= postRepository.findAll();
// Then
PostEntity posts=postsList.get(0);
assertThat(posts.getTitle()).isEqualTo(savedPost.getTitle());
assertThat(posts.getContent()).isEqualTo(savedPost.getContent());
assertThat(posts.getUser().getId()).isEqualTo(savedPost.getUser().getId());
}
}
💚 BoardControllerTest
- 게시글 생성, 수정 통신이 잘 이루어지는지에 대한 테스트 코드
- MocMvc 사용하여 통신
package com.gdsc_teamb.servertoyproject.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gdsc_teamb.servertoyproject.domain.post.domain.HeartEntity;
import com.gdsc_teamb.servertoyproject.domain.post.domain.PostEntity;
import com.gdsc_teamb.servertoyproject.domain.repository.PostRepository;
import com.gdsc_teamb.servertoyproject.domain.user.domain.UserRepository;
import com.gdsc_teamb.servertoyproject.domain.user.domain.UserEntity;
import com.gdsc_teamb.servertoyproject.dto.boardDto.BoardDto;
import com.gdsc_teamb.servertoyproject.dto.boardDto.BoardUpdateDto;
import jakarta.transaction.Transactional;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.*;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.util.AssertionErrors.fail;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@Transactional
class BoardControllerTest {
// 테스트에 사용할 서버의 랜덤 포트 번호를 할당
@LocalServerPort
private int port;
@Autowired
private PostRepository postRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private MockMvc mockMvc;
@Test
@DisplayName("게시물 등록 테스트")
public void registerPost() throws Exception{
final String TITLE = "title-test";
final String CONTENT = "content-test";
//Given 등록할 게시글 생성
UserEntity savedUser = userRepository.save(UserEntity.builder()
.email("abc@abc.com")
.password("password1234")
.nickname("nickname")
.phone("01012345678")
.build());
BoardDto boardDto=BoardDto.builder()
.title(TITLE)
.content(CONTENT)
.user(savedUser)
.build();
String url="http://localhost:" + port + "/api/boards";
// when
mockMvc.perform(post(url)
.contentType(APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(boardDto)))
.andExpect(status().isOk())
.andDo(print());
// then
// 저장한 데이터만 조회
List<PostEntity> savedPosts = postRepository.findAllByUser(savedUser);
assertThat(savedPosts).hasSize(1); // 사용자에게 속하는 게시글 중에서 검사
PostEntity postSaved = savedPosts.get(0);
assertThat(postSaved.getTitle()).isEqualTo(TITLE);
assertThat(postSaved.getContent()).isEqualTo(CONTENT);
assertThat(postSaved.getUser().getId()).isEqualTo(savedUser.getId());
}
@Test
@DisplayName("게시물 수정 테스트")
public void updatePost() throws Exception {
// Given 등록된 게시물과 수정할 게시물
UserEntity savedUser = userRepository.save(UserEntity.builder()
.email("abc@abc.com")
.password("password1234")
.nickname("nickname")
.phone("01012345678")
.build());
PostEntity savedPost = postRepository.save(PostEntity.builder()
.title("title")
.content("content")
.user(savedUser)
.build());
Long updateId = savedPost.getId();
String expectedTitle = "title2";
String expectedContent = "content2";
BoardUpdateDto boardUpdateDto= BoardUpdateDto.builder()
.title(expectedTitle)
.content(expectedContent)
.build();
String url="http://localhost:" + port + "/api/boards/"+updateId;
// when
mockMvc.perform(put(url)
.contentType(APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(boardUpdateDto)))
.andExpect(status().isOk());
// then
// 저장한 데이터만 조회
List<PostEntity> savedPosts = postRepository.findAllByUser(savedUser);
assertThat(savedPosts).hasSize(1); // 사용자에게 속하는 게시글 중에서 검사
PostEntity postSaved = savedPosts.get(0);
assertThat(postSaved.getTitle()).isEqualTo(expectedTitle);
assertThat(postSaved.getContent()).isEqualTo(expectedContent);
}
}
'Server > Spring Boot' 카테고리의 다른 글
[Spring Boot] Toy Project 게시판 만들기 (3) - 조회, 삭제 (1) | 2023.12.18 |
---|---|
[Chapter 06] AWS 서버 환경을 만들어보자 - AWS EC2 (1) | 2023.12.17 |
[Chapter 03] 스프링 부트에서 JPA로 데이터베이스 다뤄보자 (2) - 수정/조회 (1) | 2023.12.17 |
[Spring Boot] 스프링의 계층구조, 요청 응답 과정 (1) | 2023.12.17 |
[Spring Boot] 게시판 만들기 (1) - ERD 설계, MySQL 연결 (0) | 2023.11.13 |