[Spring] Restful Api example (with Hateoas) 구현
환경
- https://spring.io/guides/gs/rest-hateoas/ 의 기본 예제를 다운 받아 사용하였다.
- Spring Boot (2.2.2 RELEASE)
- H2
- JPA
소스코드
Member
@Entity
@Table(name = "member")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Builder
@EqualsAndHashCode(of = "id")
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
private Integer age;
@Enumerated(EnumType.STRING)
private Grade grade;
private Member(String name, Integer age, Grade grade){
this.name = name;
this.age = age;
this.grade = grade;
}
}
MemberRepository
@Transactional(readOnly = true)
@RepositoryRestResource
public interface MemberRepository extends JpaRepository<Member, Long> {
Member findByName(@Param("name") String name);
}
@RepositoryRestResource
-
컨트롤러를 만들지 않아도
내부적으로 Rest Api
가 만들어진다. -
옵션을 추가로 줄 수 있다.
(collectionResourceRel = "account", path = "account")
옵션을 주게된다면."account": { // collectionResourceRel 에 추가한 속성 "href": "http://localhost:8080/account{?page,size,sort}", "templated": true }, path = url path를 말한다 -> 8080/ 뒤에 account
MemberHateoas
public class MemberHateoas extends RepresentationModel<MemberHateoas>{
private final String name;
private final Integer age;
//JSON 타입을 받을 JsonCreator 생성자 생성
@JsonCreator
public MemberHateoas(@JsonProperty("name") String name, @JsonProperty("age") Integer age) {
this.name = name;
this.age = age;
}
}
@JsonCreator
-
Jackson이 name, age에 대한 POJO 인스턴스를 어떻게 만들지에 대한 정의를 시작하겠다는 의미다.
-
이때
생성자
를 통해 생성하거나setter
를 통해 생성한다. jackson이 해당 함수를 통해 객체를 생성하고 필드를 생성과 동시에 채운다. 이렇게 생성자나 팩토리 메소드를 통해 필드 주입까지 끝내버리면 setter 함수가 필요 없게 된다. jackson을 통해 역직렬화한 immutable한 객체를 얻을 수 있는 것이다.
@JsonProperty
- 생성자를 포함한 함수는 컴파일 되면 파라미터의 이름이 var0, var1 처럼 임의적으로 바뀐다.
- 그래서 jackson이 파싱된 key, value를 찾을 수 없기 때문에
JsonProperty
를 사용해 명시해줘야한다.
MemberController
@RestController
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
private final MemberRepository memberRepository;
@GetMapping("/api")
public EntityModel<Member> insertMember(@RequestParam(value = "name") String name, @RequestParam(value = "age") Integer age, Grade grade){
Member member = memberService.addMember(name, age, grade);
EntityModel<Member> memberEntityModel = new EntityModel<>(member);
memberEntityModel.add(linkTo(methodOn(MemberController.class).insertMember(name, age, grade)).withSelfRel());
return memberEntityModel;
}
}
- linkTo와 methodOn 을 사용해 controller에 가짜 링크 메소드 콜을 만들어 EntityModel에 붙인다(add)
- withSelfRel()이 Link 인스턴스를 만들어 준다.
MemberService
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
public Member addMember(String name, Integer age, Grade grade) {
Member findMember = memberRepository.findByName(name);
if(findMember == null){
Member member = Member.builder()
.name(name)
.age(age)
.grade(grade.GOLD)
.build();
return memberRepository.save(member);
}
return findMember;
}
}
작성을 완료했으니 값이 어떻게 나오는지 출력해보자.
localhost:8080
-
root 경로의 하위에 어떠한 자원이 있는지 알려준다.
- members를 가지고 있다.
-
호출방법(
/members
)을 알려준다.
/memebers
-
맨 아래 Page 처리 목록이 있다.
페이징 처리를 한 적이 없는데??
-
MemberRepository의 JpaRepository가
PagingAndSortingRepository
를 상속받기 때문에 가능하다. -
페이징 파라미터를 추가하면
_links
아래 다음과 같이 추가가된다. -
self를 제외하고, first, prev, last가 추가된 것을 확인 할 수 있다.
-
/members/1
-
멤버 한 명에 대한 상세조회를 하고 싶다면 이와 같이 하면 된다.
POST 생성 요청
/members/search
- MemeberRpository에
findByName
쿼리 메소드를 추가했었다. - 그로인해 _links 아래 search 가 추가된 것을 볼 수 있다.
클릭을 하면 마찬가지로 사용방법에 대한 정의가 나온다.
자동적으로 쿼리가 바인딩 처리 된 것을 확인 할 수 있다.
/members/search/findByName?name=n1tjrgns
이런식으로 검색을 하면 된다.
샘플 완성
P.S
jpa로 hateoas 예제를 작성한김에 spring-guides에 PR을 날려봤으나 일주일째 묵묵부답이다..ㅎ
오픈소스 기여하기는 힘드네
https://github.com/spring-guides/gs-accessing-data-rest/pull/32
참고