// Memo.java
@Getter
@Setter
@NoArgsConstructor
public class Memo {
private Long id;
private String username;
private String contents;
// 생성자 Constructor
public Memo(MemoRequestDto requestDto) {
this.username = requestDto.getUsername();
this.contents = requestDto.getContents();
}
public void update(MemoRequestDto requestDto) {
this.username = requestDto.getUsername();
this.contents = requestDto.getContents();
}
}
// MemoRequestDto.java
@Getter
public class MemoRequestDto {
private String username;
private String contents;
}
// MemoResponseDto.java
@Getter
public class MemoResponseDto {
private Long id;
private String username;
private String contents;
public MemoResponseDto(Memo memo) {
this.id = memo.getId();
this.username = memo.getUsername();
this.contents = memo.getContents();
}
}
// MemoController.java
...
@RestController
@RequestMapping("/api")
public class MemoController {
private final Map<Long, Memo> memoList = new HashMap<>();
// 메모 추가
@PostMapping("/memos")
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
// RequestDto -> Entity로 수정 (DB 저장을 위함)
Memo memo = new Memo(requestDto);
// Memo의 Max ID를 찾아야함 (check)
// keySet은 Map의 key value중 key 값을 참조하고 max는 그중 최대 수를 가져오는 Collections의 메서드
Long maxId = memoList.size() > 0 ? Collections.max(memoList.keySet()) + 1 : 1;
memo.setId(maxId);
// DB 저장
memoList.put(memo.getId(), memo);
// Entity -> ResponseDto로 변환 후 반환
MemoResponseDto memoResponseDto = new MemoResponseDto(memo);
return memoResponseDto;
}
@GetMapping("/memos")
public List<MemoResponseDto> getMemos() {
// Map To List
List<MemoResponseDto> responseList = memoList.values().stream()
.map(MemoResponseDto::new).toList();
return responseList;
}
@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
// 메모가 실제로 db에 존재하는지 체크
if (memoList.containsKey(id)) {
// 해당 메모 가져오기
Memo memo = memoList.get(id);
// 가져온 메모를 수정
memo.update(requestDto);
return memo.getId();
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}
@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
// 메모가 존재하는지 체크
if (memoList.containsKey(id)) {
//해당 메모 삭제
memoList.remove(id);
return id;
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}
}
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto)
$.ajax({
type: "POST",
url: "/api/memos",
contentType: "application/json",
data: JSON.stringify(data),
success: function (response) {
alert('메시지가 성공적으로 작성되었습니다.');
window.location.reload();
}
});
Front-End에서 이렇게 호출할 경우 서버는 JSON 자료를 받게 된다.
JSON 자료를 미리 양식에 맟춰 만들어둔 MemoRequestDto에 받도록 한다. (@RequestBody 필요)
ex)MemoRequestDto 타입의 requestDto 객체에는 API호출로 받아온 JSON 자료의 내용이 삽입됨 {"username":"straycat","contents":"hello"}
id값은 서버에서 생성 및 부여하므로 MemoRequestDto의 경우 id필드 존재하지 않음
다음 작업은 requestDto를 Entity(Memo 타입 객체)로 수정해야함
이는 DB 저장을 위함이고, 이를 위해선 id값을 제공해야함
Q. 그냥 Memo 타입 객체 하나로 다 하면 될 것 같은데, 굳이 반복적인 느낌으로 requestDto, responseDto를 별도로 작성해서 사용하는 이유는?
-> DTO 분리를 통해 보안, 유연성, 안정성을 위함
maxId값 부여 (memoList의 key값과 Memo객체의 id필드는 동일하게 맟춤)
Long maxId = memoList.size() > 0 ? Collections.max(memoList.keySet()) + 1 : 1;
memo.setId(maxId);
memoList.put(memo.getId(), memo);
위에서 memo의 id필드를 setId(maxId)로 지정했으므로, memoList의 key값은 memo.getId()로 참조해서 동일하게 설정한다.
ex) memoList[0] >>> [1, {"id":1,"username":"straycat","contents":"hello"}]
헷갈릴 수 있지만 헷갈리지 말 것.
마지막으로 새로 설정한 Memo객체 Entity를 ResponseDto로 변환 후 리턴
MemoResponseDto memoResponseDto = new MemoResponseDto(memo);
return memoResponseDto;
function writePost() {
// ...
$.ajax({
type: "POST",
url: "/api/memos",
contentType: "application/json",
data: JSON.stringify(data),
success: function (response) { // {id: 5, username: "abc", contents: "...", modifiedAt: "..."}
alert('메시지가 성공적으로 작성되었습니다.');
// 새로고침 대신 응답 데이터로 바로 추가
addHTML(response.id, response.username, response.contents, response.modifiedAt);
$('#contents').val(''); // textarea 초기화
}
});
}
Q. Front-End측에서 response에 전달한 responseDto를 사용하지 않을 때도 있지 않나? 굳이 매번 responseDto를 반환해야 하는가? 응답 코드와 메세지로 충분하지 않은가?
A: 실무적인 관례로서 해석
// 파일 업로드처럼 용량 큰 경우
@PostMapping("/upload")
public UploadResponse upload() {
return new UploadResponse(fileId, fileUrl); // 전체 파일은 리턴하지 않음
}
// 배치 처리
@PostMapping("/batch")
public void processBatch() {
// 비동기 처리, 결과는 나중에 조회
}
// Map To List
List<MemoResponseDto> responseList = memoList.values().stream()
.map(MemoResponseDto::new).toList();
Map<Long, Memo> memoList = {
1: Memo{id=1, username="abc", contents="memo1"},
2: Memo{id=2, username="xyz", contents="memo2"}
};
// 1단계: values() - Map의 값들만 추출 -> key / value중 value만
// List는 key값을 지니지 않으므로 key는 버림
Collection<Memo> values = memoList.values();
// [Memo{id=1, ...}, Memo{id=2, ...}]
// 2단계: stream() - Stream으로 변환 (반복 처리 준비)
Stream<Memo> stream = values.stream();
// 3단계: map(MemoResponseDto::new) - 각 Memo를 ResponseDto로 변환
// MemoResponseDto::new는 memo -> new MemoResponseDto(memo)와 동일
Stream<MemoResponseDto> dtoStream = stream.map(MemoResponseDto::new);
// 결국 1단계에서 만들어둔 Memo객체 Collection의 각 index를 순회하며 new MemoResponseDto(memo)로 생성하는 것
// 4단계: toList() - Stream을 List로 수집
List<MemoResponseDto> responseList = dtoStream.toList();
//map(MemoResponseDto::new) 이것과 map(memo -> new MemoResponseDto(memo))이것이 동일함을 기억
//for문으로도 가능하다
List<MemoResponseDto> responseList = new ArrayList<>();
for (Memo memo : memoList.values()) {
responseList.add(new MemoResponseDto(memo));
}
$.ajax({
type: 'GET',
url: '/api/memos',
success: function (response) {
for (let i = 0; i < response.length; i++) {
let message = response[i];
let id = message['id'];
let username = message['username'];
let contents = message['contents'];
let modifiedAt = message['modifiedAt'];
addHTML(id, username, contents, modifiedAt);
}
}
})
Q. 클라이언트는 response 파라미터의 자료형이 List인것을 어떻게 인식하고 처리할 수 있는가?
A. Spring이 자동으로 JSON배열로 변환해서 보내고 있기 때문임
서버 -> 클라이언트 변환 과정을 살펴보자
// 1. 컨트롤러가 List 반환
@GetMapping("/memos")
public List<MemoResponseDto> getMemos() {
return responseList; // Java List
}
// 2. Spring의 Jackson 라이브러리가 자동으로 JSON 변환
// List<MemoResponseDto> → JSON 배열
[
{"id":1, "username":"abc", "contents":"memo1", "modifiedAt":"2024-01-01"},
{"id":2, "username":"xyz", "contents":"memo2", "modifiedAt":"2024-01-02"}
]
// 3. 클라이언트는 JSON 배열을 받음
success: function (response) {
// response는 JavaScript 배열 객체
console.log(response.length); // 2
console.log(response[0].id); // 1
console.log(response[0].username); // abc
}
@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
// 메모가 실제로 db에 존재하는지 체크
if (memoList.containsKey(id)) {
...
// 가져온 메모를 수정
memo.update(requestDto);
return memo.getId();
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
// 메모가 존재하는지 체크
if (memoList.containsKey(id)) {
//해당 메모 삭제
memoList.remove(id);
return id;
} else {
throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
}
}