이 번장에서는, 앞장에서 페이징 처리한 데이터 결과를 RestAPI를 통해
데이터베이스의 테이블 데이터를,검색 API화 하는것을 진행해보겠습니다.
페이징할 Entity체크
| User | Group |
|---|---|
@Entity
public class User {
@Id
@GeneratedValue
@Column(name = "USER_ID")
private Integer id;
private String name;
private String email;
@ManyToOne
@JoinColumn(name = "GROUP_ID")
private GroupInfo groupInfo;
| @Entity
public class GroupInfo {
@Id
@GeneratedValue
@Column(name = "GROUP_ID")
private Integer id;
private String name;
@OneToMany(mappedBy = "groupInfo", cascade = {CascadeType.PERSIST},fetch=FetchType.EAGER)
private Set<User> users;
|
우리는, List<User> 를 Json화 하여 데이터를 뿌려줄것입니다. 하지만 위 Entity 구조에 어떠한게 문제가 있을까요?
User에서는 ManyToOne으로 Group정보를 하나를 참조하고, GroupInfo에서는 OnToMay로 다시 User객체를 다수개 참조 하려할것입니다.
결국 참조를 무한으로 재귀적으로 반복하는, 무한 루프에 빠지게 됩니다.
List<GroupInfo>를 리스트화하여 뿌려준다면, 그룹별 사용자 리스트가 올바르게 표현이 될것이나....
이 케이스에서는 Group 에 포함된 사용자 리스트가 Json화 안되게끔 무시 플래그를 넣어야합니다.
JPA를 통해 Table - CLASS -Json 3가지 성격이 다른 엔티티를 통합하여 일괄적인 처리가 될수있게 하였지만
양방향 참조는 JSON에 위배됩니다.(재귀적 무한루프)
JSON표현해 한해 단방향으로 만들수 있습니다.
관련이슈에대한 토론:
다시 역참조가 안되게, GroupInfo의 users의 참조를 끊어주겠습니다.( @JsonBackReference 동일한 효과)
@Entity
public class GroupInfo {
@Id
@GeneratedValue
@Column(name = "GROUP_ID")
private Integer id;
private String name;
@JsonIgnore
@OneToMany(mappedBy = "groupInfo", cascade = {CascadeType.PERSIST},fetch=FetchType.EAGER)
private Set<User> users;
UserPageRepo 개선
public interface UserPageRepo extends Repository<User, Long>{
Page<User> findByNameContaining(String name,Pageable pageRequest);
}
UserPagerepo는 앞장에서 설명을 하였으며, 사용자의 이름이 포함된 검색을 하고 페이징처리가 되는 인터페이스를
추가하겠습니다. 구현은 필요없으며,, 지정된 함수명에따라 아래와같이 SQL문과 동일한 효과의 기능이 수행됩니다.
- findByName : where name = ??
- findByNameContaining : where name like '%??%'
STS(Spring Tool Suite) 에서, 입력한 키워드에서 컨트롤+Space를 누르면, 쿼리기능에 관련된
인터페이스가 자동완성으로 확인가능하며..., 어떠한 SQL문이 수행될것이다라고 유추할수가 있습니다.
UserSearchAPI 작성
package com.example.demo;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(path="/") //이 클래스의 루트 Router를 설정한다.
public class UserSearchAPI {
@Autowired
private UserPageRepo userPageRepo;
@GetMapping(path="/usersearch") //이 멤버함수의 하위 주소를 설정한다.
public @ResponseBody List<User> userSearch(
@RequestParam String name,
@RequestParam int pagenum,
@RequestParam int pagesize
){
PageRequest pageReq = new PageRequest(pagenum, pagesize);
Page<User> userPage =userPageRepo.findByNameContaining(name,pageReq);
return userPage.getContent();
}
}
RestAPI는 처음 소개되는거라, 해당 Class의 Full소스를 가지고 설명하겠습니다.
각 어노테이션은 아래와 같은 역활을 합니다.
- Controller : 해당 클래스가 WebAPI의 역활을 하는것을 명시
- RequestMapping : 해당클래스의 루트 Route를 설정합니다.
- GetMapping : 실제 기능이 작동하는 함수에 하위 Route를 설정합니다.
- ResponseBody : 일반적인 웹 컨텐를 반환하겠다란 의미
위 작성된 REST API는 아래와 같이 검색 API의 기본기능을 갖추게 됩니다.
http://localhost:8080/usersearch?name=minsu&pagenum=0&pagesize=5
==> 웹결과
[{"id":1,"name":"minsu7","email":"min7@x.com","groupInfo":{"id":1,"name":"학생"}},
{"id":2,"name":"minsu11","email":"min11@x.com","groupInfo":{"id":1,"name":"학생"}},
{"id":3,"name":"minsu75","email":"min75@x.com","groupInfo":{"id":1,"name":"학생"}},
{"id":4,"name":"minsu80","email":"min80@x.com","groupInfo":{"id":1,"name":"학생"}},
{"id":5,"name":"minsu83","email":"min83@x.com","groupInfo":{"id":1,"name":"학생"}}]
