시리즈 포스팅!!
SprintBoot로 게시판 만들기 ②📝(API명세서, ERD작성) - 1
SprintBoot로 게시판 만들기 ②📝(CRUD 구현_설정부) - 2
SprintBoot로 게시판 만들기 ②📝(CRUD 구현_개발) - 3
SprintBoot로 게시판 만들기 ②📝(CRUD 구현_테스트) - 4
3-5. `@Repository` 생성
JPA는 공부할 내용이 꽤 많은거같다. 종류도 많고 class 들어가서 구경을 좀해봤는데 여러가지 뭐가 많드라..
일단 `JpaRepository` 를 상속받아 Repository 인터페이스 정의를 했다.
이것만으로도 JPA가 엔티티에 대한 기본 CRUD 메서드 제공해준다. (save, findById, deleteById 등)
`JpaRepository<T, ID>`로 T는 관리할 엔티티 클래스를 작성하고 ID는 해당 엔티티의 PK타입을 적는다.
@Repository
public interface BoardRepository extends JpaRepository<BoardVO, Long> {
// 기본 CRUD 메서드 제공됨 (save, findById, deleteById 등)
}
3-6. `interface BoardService` 구현 (표준 제공용)
인터페이스를 사용한 이유는 정석적인 이유는 여러 구현체를 인터페이스로 다룰수있고 확장성과~ 어쩌고ㅋㅋ
나중에 테스트도 쉬울거같고 예제 프로젝트에서는 꼭필요한건 아닌거같지만 정 석 대 로 수행했다.
public interface BoardService {
BoardResponseDto getBoard(Long id);
List<BoardListResponseDto> getBoardList();
BoardResponseDto createBoard(BoardCreateRequestDto requestDto);
BoardResponseDto updateBoard(Long id, BoardUpdateRequestDto requestDto);
void deleteBoard(Long id, String password);
}
3-7. `@Service`구현
해당 인터페이스를 BoardServiceImpl파일에 서비스 구현했다.
엔티티 DTO변환과 트랜젝선 관리 유효성을 신경써서 구현했다.
그리고 테스트를 하다보니 예외처리부분이 아쉬워서 예외처리를 추가해주었다. (요구사항에 없지만 찝찝한부분!)
일단 게시판 비밀번호가 틀리다던가 게시글자체의 존재를 확인하는것은 필수 요구사항이었고
예를 들어 입력해야할 데이터가 들어오지않았다거나
나같은 경우는 사용자 테이블과 게시판테이블의 작성자와 사용자를 외래키로 연결했기때문에
사용자테이블에 없는 user가 게시판에 글을 쓸수 없었는데 그경우 처리를 해주었다.
//작성
@Override
@Transactional
public BoardResponseDto createBoard(BoardCreateRequestDto requestDto) {
//유효성 체크
boardValidator.validateUserExists(requestDto.getWriter()); //등록되지 않은유저일경우
//정적 메서드 방식을 사용했음 BoardVO에 추가함
BoardVO boardVO = BoardVO.from(requestDto);
boardRepository.save(boardVO);
return BoardResponseDto.from(boardVO); // DTO로 변환해 반환
}
그리고 유효성 체크를 하는 `BoardValidator.java` 파일을 생성했다.
public class BoardValidator {
...(생략)
/** 사용자 존재 여부 확인 */
public void validateUserExists(String userId) {
if (!userRepository.existsById(userId)) {
throw new UserNotFoundException("등록되지 않은 사용자입니다. 사용자 아이디 : " + userId);
}
}
...(생략)
}
예외처리할 클래스도 만들어주고
package com.example.SpringBootStudy99.exception;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
`@RestControllerAdvice`를 이용해서 실제로 예외 터졌을때 이동하는 `ExceptionHandler`인 `CommonExceptionHandler.java` 파일을 생성했다.
@RestControllerAdvice
public class CommonExceptionHandler {
// 게시글이 없을 때
//api 테스트할때 파라미터 빼먹었을경우
// 비밀번호 틀릴 때
// 그 외 모든 예외
// 등록되지않은 유저일때
@ExceptionHandler(DataIntegrityViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<?> handleDataIntegrityViolation(DataIntegrityViolationException ex) {
return ApiResponse.error(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
}
}
부연 다른 예외처리 예시
api 테스트할때 파라미터 빼먹었을경우에도 예외처리를 해줬는데 ㅋㅋ
이걸 왜했냐면 처음에 api 테스트로 게시글 작성하고 수정하는데 비밀번호틀렸다는 예외에러메시지가 죽어도 안나오는거임..
이유는
1. 작성시 pwd가 필수값이 아니라 다 null로 들어감
2. 수정할때 생각없이 그냥 테스트하는데 body에 title이랑 content파라미터값만 줌 ㅜㅜㅋ
에러가 빵빵터지는데 안만들수가 없었음 ㅠㅠㅠㅠㅠ
추가로 처리해야할 부분은 우선 당연히 `CommonExceptionHandler.java` 파일에 메서드를 만들어주고
나는 스트림이나 람다 이런걸 안써봤는데 (회사 1.7씀) 이번 프로젝트를 하면서 약간 의식적으로 열심히ㅠ 공부해서 써보고있다.
옵셔널은 내가 회사플젝에 자바 클래스까서 함 만들어서 써봤음ㅋㅋ
스트림이 함수방식이라 가독성좋고 병렬처리가가능하며 머 기타등등 장점이있다는데 와우 미친 헷갈리던데.. 추가로 포스팅하면서 한번 더 공부해야겠다 ㅠㅠ
내가 구현한 기본문법수준의 람다 스트림은 할만한데 깊게 찾아볼수록 뭔말인지 한개도 모르겠던데 ;;
일단 나는 데이터를 가공할때 많이 사용했다 (엔티티랑 DTO!)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Void>> handleValidationException(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.findFirst()
.orElse("잘못된 입력입니다.");
return ResponseEntity.badRequest().body(ApiResponse.error(message));
}
`BoardCreateRequestDto.java` 에 `@NotBlank` 어노테이션을 달아주면 끝이다 ~
// 게시글 작성 요청 DTO
public class BoardCreateRequestDto {
@NotBlank(message = "제목은 필수입니다.")
private String title;
@NotBlank(message = "작성자는 필수입니다.")
private String writer;
@NotBlank(message = "비밀번호는 필수입니다.")
private String boardPwd;
@NotBlank(message = "내용은 필수입니다.")
private String content;
private LocalDateTime rgstDt;
private LocalDateTime udtDt;
}
3-8. @RestController` 구현
생성 후 API 명세서대로 `@RequestMappin` , ` @GetMapping `, ` @PostMapping `, ` @PutMapping `, `@DeleteMapping `를 구현하였다.
API정석대로라면 목록조회는 boards 이렇게 하는게 맞는거같은데 우선은 위로 공통으로 빼버리는바람에 API URL이 저렇게 나왔다.
ㅋㅋ 개발자는 귀차니즘이 심해야 어쩌고~ ㅎㅋ
경로를 최대한 직관적으로 하기위해서 메서드를 다 나눴음!! 그래서 URL이 매우 심플함
POST /api/board - 게시글 등록
GET /api/board/{id} - 게시글 단건 조회
GET /api/board - 게시글 목록 조회
PUT /api/board/{id} -게시글 수정
DELETE /api/board/{id} - 게시글 삭제
엄..... 그리고 공통 응답 클래스도 만들었다.
약간 회사스타일? 근데 어차피 우리는 회사를 다닐것이기때문에..ㅜㅋ
성공했을때 첨에 200하드코딩했다가 갑자기 201 200구분하고싶어서 상태값열심히 뒤졋는데
생각해보니까 201은 작성시에만 나오는것이 아닌가.................바보 아님? ㅠㅠ
그래서 다 `ResponseEntity.ok()` 사용하고
작성부분만 이렇게 작성하였다 ㅋㅋ `ResponseEntity.status(HttpStatus.CREATED) .body(ApiResponse.success("게시글이 생성 완~.", created))`
`HttpStatus.CREATED`가 죽어도 생각안났음 ㅠ
// 성공
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(true, message, data);
}
// 실패
public static <T> ApiResponse<T> error(int status, String message) {
return new ApiResponse<>(false, message, null, status);
}
공통 응답을 하긴했지만 왕왕 기본으로 출력하는것은.. 초초간단!
@RestController
@RequestMapping("/api/board")
@RequiredArgsConstructor
public class BoardController {
private final BoardServiceImpl boardServiceImpl;
/** 전체 목록 조회 - 기본 출력 */
@GetMapping("/")
public List<BoardListResponseDto> getAllBoards() {
return boardServiceImpl.getBoardList(); // 그냥 객체 자체 리턴
}
/** 게시글 상세 조회 - 기본 출력 */
@GetMapping("/{id}")
public BoardResponseDto getDetailBoard(@PathVariable Long id) {
return boardServiceImpl.getBoard(id);
}
/** 게시글 작성 - 기본 출력 */
@PostMapping("/")
public BoardResponseDto insertBoard(@RequestBody @Valid BoardCreateRequestDto requestDto) {
return boardServiceImpl.createBoard(requestDto);
}
/** 게시글 수정 - 기본 출력 */
@PutMapping("/{id}")
public BoardResponseDto updateBoard(@PathVariable Long id,
@RequestBody @Valid BoardUpdateRequestDto requestDto) {
return boardServiceImpl.updateBoard(id, requestDto);
}
/** 게시글 삭제 - 반환 없음 (void) */
@DeleteMapping("/{id}")
public void deleteBoard(@PathVariable Long id, @RequestParam String password) {
boardServiceImpl.deleteBoard(id, password);
}
}
'공부일기.. > Spring' 카테고리의 다른 글
| [Spring Boot 게시판 ] JWT 로그인 구현 하기 (0) | 2025.07.01 |
|---|---|
| [Spring Boot 게시판 ②] CRUD API , Postman 테스트(4/4) (0) | 2025.06.24 |
| [Spring Boot 게시판 ②] CRUD API 구현 (2/4) (0) | 2025.06.23 |
| [Spring Boot 게시판 ②] CRUD API 설계 (1/3) (0) | 2025.06.20 |
| [Spring Boot 게시판 만들기①] 테크스펙, 환경설정, 유스케이스 (0) | 2025.06.19 |