기능 | Method | URL | Return |
---|---|---|---|
전체 게시글 목록 조회 | GET | /board | List |
선택한 게시글 조회 | GET | /board/{id} | Post |
게시글 작성 | POST | /board | Post |
선택한 게시글 수정(변경) | PUT | /board/{id} | Long |
선택한 게시글 삭제 | DELETE | /board/{id} | Long |
//Client <--DTO--> 여기!! Controller <--DTO--> Service <--DTO--> Repository <--Domain(Entity Class)--> DB
package com.sparta.hanghaeboard.controller;
import com.sparta.hanghaeboard.dto.BoardRequestDto;
import com.sparta.hanghaeboard.dto.BoardResponseDto;
import com.sparta.hanghaeboard.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController //JSON 데이터 타입으로 response 해줄것이므로 -> 이걸 달아주지 않으면, 각각의 API에 responsebody를 달아줘야하는 번거로움
@RequiredArgsConstructor //final 선언할때 스프링에게 알려줌
public class BoardController {
//BoardService 와 연결
private final BoardService boardService;
//게시글 작성
@PostMapping("/board") //request 종류: POST
//BoardResponseDto: 반환 타입. createBoard: 메소드명(원하는대로)
//@RequestBody: POST 안에 저장된 body 값들을 key:value 형태(JSON 타입)로 짝지음. body 에 들어오는 데이터들을 가지고오는 역할 --> Controller 에서만 들어가는 부분
//BoardRequestDto: JSON 타입으로 넘어오는 데이터를 받는 객체(데이터를 저장할 공간)
//requestDto: 매개변수
public BoardResponseDto createBoard(@RequestBody BoardRequestDto requestDto) {
//매개변수 requestDto 를 메소드 createBoard 를 사용해서, boardService 로 반환(boardService 와 연결)
return boardService.createBoard(requestDto);
}
//전체 게시글 목록 조회
@GetMapping("/board") //request 종류: GET
//List 타입
//BoardResponseDto: 반환 타입. getListBoards: 메소드명. (): 전부 Client 에게로 반환하므로 비워둠
public List<BoardResponseDto> getListBoards() {
//getListBoards 메소드를 사용해서, boardService 와 연결
return boardService.getListBoards();
}
//선택한 게시글 조회
@GetMapping("/board/{id}") //request 종류: GET
//BoardResponseDto: 반환 타입. getBoards: 메소드명
//@PathVariable: URL 경로에 변수를 넣어주는 것
//@PathVariable Long id: PathVariable 방식으로 id 값을 가져온다 --> 전체 게시글 목록에서 id 값으로 각각의 게시글을 구별
public BoardResponseDto getBoards(@PathVariable Long id) { //optional: 이게 왜 쓰였는지 기술매니저님께 여쭤보기!! //List 쓰면 안 되는 걸까?
//id 값을 담은 getBoard 메소드를 사용해서, boardService 와 연결
return boardService.getBoard(id);
}
//선택한 게시글 수정(변경)
@PutMapping("/board/{id}") //request 종류: PUT
//Long: 반환 타입? updateBoard: 메소드 명
//@RequestBody: POST 안에 저장된 body 값들을 key:value 형태(JSON 타입)로 짝지음 --> Controller 에서만 들어가는 부분
//BoardRequestDto: JSON 타입으로 넘어오는 데이터를 받는 객체
//requestDto: 매개변수
public BoardResponseDto updateBoard(@PathVariable Long id, @RequestBody BoardRequestDto requestDto) { //이 부분만 Long 타입? BoardResponseDto?
//id 값을 담은 getBoard 메소드를 사용해서, boardService 와 연결
return boardService.update(id, requestDto); //(id, requestDto): requestDto 에 가져올 내용으로 id 값만 적어서 id만 나오는 건가????
}
//선택한 게시글 삭제
//@DeleteMapping("/board/{id}") //request 종류: DELETE
@DeleteMapping("/board/{id}/{password}")
//public Map<String,Object> deleteBoard(@PathVariable Long id, @RequestBody BoardRequestDto requestDto) { //BoardResponseDto? Map<String,Object>?
public BoardResponseDto deleteBoard(@PathVariable Long id, @PathVariable String password) {
System.out.println("password controller = " + password); //추가
return boardService.deleteBoard(id, password); //requestDto? password?
}
}
@RequiredArgsConstructor 가 없다면?
public class BoardController {
private final BoardService boardService;
@Autowired //생성자 주입
public BoardController (BoardService boardService) {
this.boardService = boardService;
}
...
}
해당 코드 목적?
BoardController 가 Bean 에 등록될 때, BoardService 도 같이 Bean 에 등록되도록 한다.
참고: @RequiredArgsConstructor 을 통한, 생성자 주입
//Client <--DTO--> Controller <--DTO--> 여기!! Service <--DTO--> Repository <--Domain(Entity Class)--> DB
package com.sparta.hanghaeboard.service;
import com.sparta.hanghaeboard.dto.BoardRequestDto;
import com.sparta.hanghaeboard.dto.BoardResponseDto;
import com.sparta.hanghaeboard.entity.Board;
import com.sparta.hanghaeboard.repository.BoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; //import jakarta.transaction.Transactional; 사용했더니 readOnly에 오류 발생
import java.util.ArrayList; //-->원인: readOnly 기능을 제공하는 Transactional이 아니라 다른 동명의 Transactional를 import했기 때문
import java.util.List;
@Service //서비스라는 걸 알려주는 어노테이션
@RequiredArgsConstructor //final 선언할때 스프링에게 알려줌
public class BoardService {
//BoardRepository 와 연결.
//final: 서비스에게 꼭 필요함을 명시
private final BoardRepository boardRepository;
//게시글 작성
@Transactional //업데이트를 할 때, DB에 반영이 되는 것을 스프링에게 알려줌
//BoardResponseDto: 반환 타입, createBoard: 메소드명(원하는대로)
//BoardRequestDto: JSON 타입으로 넘어오는 데이터를 받는 객체
//requestDto: 매개변수 --> controller 에서 넘어온
public BoardResponseDto createBoard(BoardRequestDto requestDto) {
//Board: Entity 명
//매개변수 requestDto 를 넣을 새로운 board 객체 생성 --> 텅 빈 상태
//가지고온 데이터(requestDto)를 넣음
Board board = new Board(requestDto); // 테이블의 한 줄이 됨
//boardRepository 안에 데이터가 들어간 객체 board 를 save(저장 함수 역할)한다
boardRepository.save(board);
//데이터가 들어간 객체 board 를 BoardResponseDto 로 반환
//(board): BoardResponseDto 에서 만든 생성자와 연결되는 부분 --> ()안에는 만들어진 생성자 형태 그대로 나타남
//예시. (String msg, int statusCode) 여기 두 개를 넣어야 함. --> 여기선 board 1개 밖에 없으므로
return new BoardResponseDto(board);
}
//전체 게시글 목록 조회
@Transactional(readOnly = true)
//BoardResponseDto: 반환 타입. getListBoards: 메소드명. (): 전부 Client 에게로 반환하므로 비워둠
public List<BoardResponseDto> getListBoards() {
//boardRepository 와 연결해서, 모든 데이터들을 내림차순으로, List 타입으로 객체 Board 에 저장된 데이터들을 boardList 안에 담는다
List<Board> boardList = boardRepository.findAllByOrderByModifiedAtDesc(); //주의. boards 와 board
//boardResponseDto 를 새롭게 만든다 --> 텅 빈 상태 (빈 주머니 상태?)
List<BoardResponseDto> boardResponseDto = new ArrayList<>();
//반복문을 이용하여, boardList 에 담긴 데이터들을 객체 Board 로 모두 옮긴다
for (Board board : boardList) {
//board 를 새롭게 BoardResponseDto 로 옮겨담고, BoardResponseDto 를 boardResponseDto 안에 추가(add)한다
boardResponseDto.add(new BoardResponseDto(board));
}
//최종적으로 옮겨담아진 boardResponseDto 를 반환
return boardResponseDto;
}
//선택한 게시글 조회
@Transactional(readOnly = true)
//BoardResponseDto: 반환 타입. getBoard: 메소드명. (Long id): Client 에게로 반환할 값
public BoardResponseDto getBoard(Long id) {
//Board: Entity 명
//boardRepository 와 연결해서, id 를 찾는다
//orElseThrow: 예외 처리 --> 예외 발생 시, "아이디가 존재하지 않습니다." 를 출력
Board board = boardRepository.findById(id).orElseThrow(
() -> new IllegalArgumentException("아이디가 존재하지 않습니다.") //RuntimeException? IllegalArgumentException?
);
//데이터가 들어간 객체 board 를 BoardResponseDto 로 반환
return new BoardResponseDto(board); //추가
}
//선택한 게시글 수정(변경)
@Transactional // 업데이트를 할 때, DB에 반영이 되는 것을 스프링에게 알려줌
//BoardResponseDto 대신 ResponseEntity<?> 사용 가능 -> 어떤 클래스 타입이든 들어올 수 있음
public ResponseEntity<?> update(Long id, BoardRequestDto requestDto) {
// DB 에 저장된 게시글이 있는지 확인 -> 있으면, 해당 데이터를 board 에 담음
Board board = boardRepository.findById(id).orElseThrow(
() -> new IllegalArgumentException("아이디가 존재하지 않습니다.")
);
//System.out.println(requestDto.getPassword());
//System.out.println(board.getPassword());
// 등록된 Password 와 수정 할 때 받은 Password 를 비교
if (board.getPassword().equals(requestDto.getPassword())) {
board.update(requestDto);
//BoardResponseDto boardResponseDto = new BoardResponseDto(board);
//return boardResponseDto;
return ResponseEntity.ok(new BoardResponseDto(board)); //위의 두 줄을 한 줄에 쓴 형태(인라인화)
} else {
//return board.getId();
return new ResponseEntity<>("비밀번호가 일치하지 않습니다.", HttpStatus.UNAUTHORIZED.value());
}
}
//선택한 게시글 삭제
@Transactional
//public Map<String, Object> deleteBoard(Long id, BoardRequestDto requestDto) { //, BoardRequestDto requestDto 추가 BoardResponseDto?? Map<String, Object>??
public BoardResponseDto deleteBoard (Long id, String password) {
Board board = boardRepository.findById(id).orElseThrow(
() -> new IllegalArgumentException("게시글이 존재하지 않습니다.")
);
System.out.println("board password= " + board.getPassword());
System.out.println("password = " + password);
//Map<String, Object> response = new HashMap<>(); response? reponse?
//if (requestDto.getPassword().equals(board.getPassword())) {
if(board.getPassword().equals(password))
boardRepository.deleteById(id); //deleteById? delete?
//response.put("success",true);
return new BoardResponseDto("게시글 삭제 성공", HttpStatus.OK.value());
//return response;
// } else {
// return new BoardResponseDto("비밀번호가 일치하지 않습니다.", HttpStatus.UNAUTHORIZED.value());
//response.put("success", false);
//return response;
}
}
게시글 작성 (BoardService)
public BoardResponseDto createBoard(BoardRequestDto requestDto) {
Board board = new Board(requestDto); // 테이블의 한 줄
// 저장. 그러나, dto 로 반환하도록 되어있음(반환 타입: BoardResponseDto)
// 그래서, 일단 saveBoard 에 저장해두고, 이것을 dto 형태로 변환함
Board saveBoard = boardRepository.save(board);
BoardResponseDto boardResponseDto = new BoardResponseDto(saveBoard); // BoardResponseDto 는 Bean 으로 등록된 객체가 아닌 순수 자바 클래스이므로, new 를 사용해서 객체를 만들어줌
return BoardResponseDto;
}
게시글 작성 (BoardResponseDto)
public class BoardResponseDto {
private String title;
private String username;
private String contents;
private LocalDateTime modifiedAt;
private LocalDateTime createdAt;
public BoardResponseDto(Board saveBoard) {
this.title = saveBoard.getTitle(); //BoardResponseDto 클래스 내부에 저장된 위의 필드값 title 에 Board 엔티티에 저장된 값을 넣음
this.username...
...
}
}
전체 게시글 목록 조회
public List<BoardResponseDto> getListBoards() {
//BoardResponseDto 타입으로 반환하도록 되어있으므로, 반환할 타입의 List를 만든다.(boardList)
List<BoardResponseDto> boardList = new ArrayList();
//DB 에서 List 형식의 Board 를 모두 찾아서, boards 에 담는다
List<Board> boards = boardRepository.findAll();
//for 문을 통해, boards 를 하나씩 하나씩 board 에 담는다.
for (Board board : boards) {
//새로운 생성자를 만들어서, Board 에 담긴 데이터들로 초기화시킨다.(BoardResponseDto.java 에서 초기화된다)
//DB 입장에서는 이 코드가 row 1개
BoardResposneDto responseDto = new BoardResposneDto(Board);
//BoardResponseDto 타입만 들어갈 수 있는 List 에, 받아왔던 boards 가 하나씩 하나씩 Board 에 담겨, BoardResposneDto 타입으로 변환되면서, BoardResponseDto 타입만 들어갈 수 있는 List 에 들어가짐
boardList.add(responseDto);
}
//반환 타입에 맞춰, board 가 아닌 boardList 로 반환
return boardList;
}
//Client <--DTO--> Controller <--DTO--> Service <--DTO--> 여기!! Repository <--Domain(Entity Class)--> DB
package com.sparta.hanghaeboard.repository;
import com.sparta.hanghaeboard.entity.Board;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
//게시글 작성
//jpa: 미리 검색 메소드를 정의 해 두는 것, 메소드를 호출하는 것만으로 스마트한 데이터 검색 가능
//JpaRepository: Entity 에 있는 데이터를 조회, 저장, 변경, 삭제 할때 Spring JPA에서 제공하는 Repository라는 인터페이스를 정의해 해당 Entity의 데이터를 사용.
//@Repository: JpaRepository 내부에 자동으로 Bean 으로 등록될 수 있는 옵션이 들어가 있으므로, 생략 가능하고 Bean 으로 자동 등록됨
public interface BoardRepository extends JpaRepository<Board, Long> { //<Entity 클래스 이름, ID 필드 타입>
//전체 게시글 목록 조회
List<Board> findAllByOrderByModifiedAtDesc(); //모두 불러와 id에 대해 내림차순 정렬
}
//Client <--DTO--> Controller <--DTO--> Service <--DTO--> Repository <-- 여기!! Domain(Entity Class)--> DB
//Board 테이블 생성
package com.sparta.hanghaeboard.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.sparta.hanghaeboard.dto.BoardRequestDto;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter //setter는 repository에서 자동으로 해주기 때문에 설정 안 함
@Entity //데이터베이스 기준으로 테이블 역할을 하는 것을 스프링에게 알려줌 --> 데이터베이스로 데이터를 날린다
@NoArgsConstructor //기본생성자를 자동으로 만듦
//Board: Entity 클래스명 <-- Timestamped 를 상속받는다
public class Board extends Timestamped {
//필드
//게시글 작성
//필드값: Id, title(제목), username(작성자), contents(내용), password
@Id
@GeneratedValue(strategy = GenerationType.AUTO) //id 자동 증가명령
private Long id;
@Column(nullable = false) // 컬럼 값이고 반드시 값이 존재해야 한다.
private String title;
@Column(nullable = false)
private String username;
@Column(nullable = false)
private String contents;
@JsonIgnore //데이터를 주고받을 때, 해당 데이터는 'ignore' 돼서 응답값에 보이지 않게됨
@Column(nullable = false)
private String password;
//생성자
//게시글 작성
//Board: 객체명 --> boardRepository 안에 데이터가 들어간 객체 board 와 연결
//requestDto: 객체 board 의 값을 넣어줄 생성자
//requestDto 안에 title, username, contents, password 값을 넣을 것이다. (길만 열어둔 상태? 통로? 주머니? 느낌?)
// BoardService.java 에서 Board board = new Board(requestDto); 을 만들었기 때문에 필요함
public Board(BoardRequestDto requestDto) { //BoardService.java 에서 객체 Board의 값을 넣어줄 생성자 requestDto를 만듦
this.title = requestDto.getTitle(); //주의!! gettitle 이 아닌, getTitle
this.username = requestDto.getUsername(); //위의 필드값 username 에 Client에게서 받아온 값(requestDto)을 넣음
this.contents = requestDto.getContents();
this.password = requestDto.getPassword();
}
//선택한 게시글 수정(변경)
public void update(BoardRequestDto requestDto) { //responseDto? boardRequestDto? boardResponseDto? requestDto? 왜?
this.title = requestDto.getTitle(); //responseDto? boardRequestDto? boardResponseDto?
this.username = requestDto.getUsername();
this.contents = requestDto.getContents();
this.password = requestDto.getPassword();
}
}
//자동으로 현재 시간을 보여줌
package com.sparta.hanghaeboard.entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Getter //Getter가 없으면 작동이 안 됨
@MappedSuperclass //상속했을 때, 자동으로 컬럼으로 인식
// Springboot 에 @EnableJpaAuditing 을 추가해줘야지만, 아래 코드가 정상 작동함
@EntityListeners(AuditingEntityListener.class) //생성,수정 시간을 자동으로 반영하도록 설정
public class Timestamped {
//createdAt, modifiedAt 컬럼 2개를 가진다
@CreatedDate //생성일자
@Column(updatable = false) //처음 작성된 날짜는 다음 날짜로 업데이트되더라도 변화 X (기존값 유지를 위해)
private LocalDateTime createdAt;
@LastModifiedDate //마지막 수정일자
private LocalDateTime modifiedAt;
}
package com.sparta.hanghaeboard.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
//게시글 작성
@Getter
@Setter
@NoArgsConstructor //기본생성자를 자동으로 만듦
//BoardRequestDto: 테이블의 데이터에 접근할 때, 완충재 역할
public class BoardRequestDto {
//Client 가 요청(request)한 데이터들(title, username, contents, password --> 이 객체들 안에 데이터가 저장됨)을 DB 로 넘긴다
private String title;
private String username;
private String contents;
private String password; //문자열도 섞여있으므로 String
}
@Getter(접근자), @Setter(설정자) 를 사용하지 않는다면?
public void setTitle(String title) {
this.title = title;
}
public void ...
...
이렇게 일일이 다 정의해줘야함
package com.sparta.hanghaeboard.dto;
import com.sparta.hanghaeboard.entity.Board;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Getter
@NoArgsConstructor
//넘어오는 데이터들(title, username, contents, modifiedAt, createdAt, id)을 Client 쪽으로 넘긴다
//--> GET 방식을 이용해서, 최종 출력된 값은 이 형태로 보인다(view)
public class BoardResponseDto {
private String title;
private String username;
private String contents;
//private String password;
private LocalDateTime modifiedAt;
private LocalDateTime createdAt;
private Long id;
private String msg;
private int statusCode;
//생성자
//객체 board 안에 데이터들을 담아둔다 (길만 열어둔 상태? 통로? 주머니? 느낌?)
public BoardResponseDto(Board board) {
this.title = board.getTitle();
this.username = board.getUsername();
this.contents = board.getContents();
//this.password = board.getPassword();
this.createdAt = board.getCreatedAt();
this.modifiedAt = board.getModifiedAt();
this.id = board.getId();
}
public BoardResponseDto(String msg, int statusCode) {
this.msg = msg;
this.statusCode = statusCode;
}
}
BoardRequestDto, BoardResponseDto, ResponseDto 가 있을 때, ResponseDto 에서 제네릭을 사용하는 이유?
데이터에 int, String 등 타입이 달라질 수 있기 때문에 유동적으로 사용하기 위함
// ResponseDto<T> 은 모든 값이 들어올 수 있는 가변성을 가지게 됨
public class ResponseDto<T> {
T data;
String msg;
int statusCode;
}
해결법 1
Edit Configurations(구성 편집) - environment variable(환경 변수) - server.port='8090'(''에는 원하는 포트번호를 넣으면 됨)으로 port번호를 변경
해결법 2: 강제 종료
cmd에서 netstat -ano를 입력 - 이 중에서 로컬 주소 8080을 찾고, 그것의 PID 번호를 찾기 - 작업 관리자에서 PID 번호를 확인하고, 해당 프로그램을 강제 종료하기