데이터베이스를 접근하는 방법은 아직 배우지 않았기 때문에,
단순히 List Collection에 Member를 CRUD 하는 코드를 짜보려고 한다.
@Getter
public class Member {
private static Long sequence = 1000L;
private final Long memberID;
private String memberName;
private String memberAddress;
public Member(String memberName, String memberAddress) {
this.memberID = ++sequence;
this.memberName = memberName;
this.memberAddress = memberAddress;
}
}
@RestController
@RequestMapping("/members")
public class MemberController {
private List<Member> memberList;
public MemberController() {
this.memberList = List.of(
new Member("John", "Seoul"),
new Member("Jack", "Busan"));
}
}
DTO로 활용할 Member 클래스를 생성하고,
Controller의 생성자에서 memberList를 초기화한다. 임의의 2개의 Member 객체를 넣어주었다.
GET은 CRUD에서 R(Read)의 역할을 하는 메서드로, 리소스를 조회한다.
@GetMapping
public List<Member> showAllMembers() {
return memberList;
}
쉽다. 리스트 자체를 리턴해주면 된다.
참고로, GET 메서드는 원래 특정 상태 코드를 지정하지 않는다.
따라서 별 문제 없이 조회에 성공했다면 200(OK) 상태 코드를 리턴해준다.
@GetMapping("/{id}")
public Optional<Member> showMemberByID(@PathVariable String id) {
for (Member m : memberList) {
if (m.getMemberID().toString().equals(id))
return Optional.of(m);
}
return Optional.empty();
}
@PathVariable로 찾고자 하는 멤버의 id값을 건네받고, 반복문을 통해 해당 id 값이 존재하면 그에 상응하는 Member 객체를 리턴해준다. 위 예시에서는 1001의 id 값을 가진 Member(John)가 존재하므로 HTTP Body에 Json 형태로 해당 객체가 변환되어 넘어왔다.
단, id 값이 리스트에 존재하지 않을 가능성이 있기 때문에 메서드의 리턴값을 Member가 아닌 Optional<Member>로 설정하였다. id 값이 존재하면 Optional의 of 메서드에 DTO 객체를 리턴하면 되지만, 존재하지 않는 경우 위 사진처럼 empty 메서드를 리턴하여 HTTP Body에 'null' 값이 넘어온다.
Optional은 null값이 존재할 수 있는 클래스를 감싸는 Wrapper 클래스이다.
POST는 CRUD에서 C(Create)의 역할을 하는 메서드로, 리소스를 등록한다.
@PostMapping()
public ResponseEntity<Member> register(@RequestBody Member member) {
memberList.add(member);
return new ResponseEntity<>(member, HttpStatus.CREATED);
}
@RequestBody로 넘어온 Json 데이터는 Object Mapper를 통해 DTO 객체와 매칭되고,
그 매칭된 member 객체를 memberList에 추가하기만 하면 된다.
참고로, POST 메서드는 상태 코드를 사용하는 것이 권장되는 메서드이다. 따라서, memberList에 데이터가 잘 추가되었다면, 200(OK) 상태 코드를 리턴해준다. 이전에 배운 ResponseEntity 클래스를 활용하여 member 객체와 상태 코드를 동시에 돌려주자. ResponseEntity 생성자의 첫 번째 파라미터는 객체, 두 번째 파라미터는 상태 코드임을 명심하자.
PUT은 CRUD에서 U(Update)의 역할을 하는 메서드로, 리소스를 수정한다. 기존에 만든 리소스가 서버(데이터베이스)에 존재하면 리소스를 업데이트하고, 리소스가 존재하지 않으면 리소스를 새로 생성하는 메서드이다.
@PutMapping("/{id}")
public ResponseEntity<Member> edit(@RequestBody Member member, @PathVariable String id) {
for (Member m : memberList) {
if (m.getMemberID().toString().equals(id)) {
m.setMemberName(member.getMemberName());
m.setMemberAddress(member.getMemberAddress());
return new ResponseEntity<>(m, HttpStatus.OK);
}
}
return register(member);
}
PUT에서는 수정할 정보의 내용을 @RequestBody로 받고, 정보를 수정할 대상의 ID를 @PathVariable로 받아야 한다. 위 예시에서는 1002번의 id를 가진 Member(Jack)의 Address를 "Gangwon"으로 수정한다.
단, id가 memberList에 존재하지 않는 경우, 리소스를 새로 생성을 해야한다. 1008번의 id를 가진 Member가 memberList에 존재하지 않으므로, Jessica에 대한 객체가 새로 memberList에 추가된다.
어? 분명 PathVariable로 넘긴 id값은 1008인데,
정작 Jessica 객체에서 부여받은 memberID는 1003이다. 어떻게 된 것일까?
우리는 id를 PathVariable를 통해 파라미터로 받고, 이를 단순히 비교하는 '기준점'으로 활용했을 뿐이지, HTTP Body (Json 형식의 데이터)에 추가하여 보낸 것이 아니다. 당연히 Object Mapper는 DTO 객체에 매칭을 해주고 싶어도 불가능한 일이 된 것이다.
만약, 클라이언트 측에서 memberID 값을 직접 지정하고 싶으면, 넘겨주는 JSON 데이터에 "memberID": 2000 이런식으로 Key-Value 값을 추가하면 된다. 그렇게 하면 Object Mapper가 Member(DTO) 객체의 memberID 값에 2000이라는 데이터를 잘 넣어줄 것이다. 그런데... 현재 로직 상 굳이 그렇게 할 이유는 없다. 오히려 클라이언트가 memberID를 건드리는 순간 sequence는 꼬이게 된다.
참고로, memberID는 Member 클래스의 생성자에서 값이 부여된다. 객체들이 만들어 질 때마다 static 변수로 선언된 sequence가 1씩 증가하면서 해당 객체의 memberID 값으로 대입된다. 이는, 객체를 수 십, 수 백 개를 생성해도 이들의 memberID가 겹치지 않는다는 뜻이고, memberID가 member 객체들을 구분할 수 있는 유일한 '식별자'가 된다.
(이 '식별자'는 추후에 데이터베이스를 공부하게 되면 왜 중요한지 알게 될 것이다.)
PUT 메서드는 응답 시 상태 코드를 '필수로' 사용해야 한다. 확실하지는 않지만, 아마 리소스가 추가된 것인지, 변경된 것인지 구분하기 위해서가 아닐까 싶다. 리소스가 변경된 경우에는 200(OK), 리소스가 새로 추가된 경우에는 201(Created)를 리턴한다. 위 PutMapping의 예시와 마찬가지로 리턴값으로 ResponseEntity 객체를 활용하되, 두 가지 경우를 구분해서 리턴한다.
리소스를 새로 생성해야 하는 경우, (= id가 리스트에 존재하지 않는 경우)
굳이 edit 메서드 안에 '리스트에 리소스 추가' 코드를 작성하지 않고,
PostMapping을 사용한 register 메서드에 member를 넘겨주면 코드를 간략화할 수 있다.
DELETE는 CRUD에서 D(DELETE)의 역할을 하는 메서드로, 리소스를 삭제한다.
@DeleteMapping("/{id}")
public ResponseEntity<Member> delete(@PathVariable String id) {
for (Member m : memberList) {
if (m.getMemberID().toString().equals(id)) {
memberList.remove(m);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
@PathVariable로 id를 받아와서 그 id에 해당하는 객체를 memberList에서 삭제한다. DELETE 메서드는 POST 메서드와 같이 상태 코드를 사용하는 것이 권장되는 메서드이므로, ResponseEntity 객체를 사용한다. 참고로, ResponseEntity 생성자에 HTTP 상태코드(HttpStatus) 하나의 파라미터만 넣어서도 사용할 수 있다.
삭제가 성공적으로 완료된 경우(=id가 memberList에 존재하는 경우), 삭제 후 특별히 응답할 내용은 없으므로 204(HttpStatus.NO_CONTENT)를 리턴하고, 삭제가 불가능한 경우(=id가 memberList에 존재하지 않는 경우), 클라이언트에서 잘못된 요청을 한 것이므로 400(HttpStatus.BAD_REQUEST)을 리턴한다. 굳이 400 상태 코드를 사용하는 방법 말고도, 삭제가 수행되지 못했다는 사실을 다양하게 표현할 수 있으니, 본인의 입맛에 맞게 사용하면 된다.