MenuRepository
public interface MenuRepository extends JpaRepository<Menu, String> {
Optional<List<Menu>> findMenuByRestaurant(long restaurantId);
}
실패
@Test public void findByIdIfNotExistScrap() throws Exception { // given List<Long> ids = Arrays.asList(100047979l, 1020517697l, 1094455540l, 1101323127l); try { for (Long id : ids) { Restaurant restaurant = restaurantsRepository .findById(id) .orElseThrow(() -> new IllegalArgumentException("restaurant cant be null")); List<Menu> menu = menuRepository .findMenuByRestaurant(id) .orElseGet(() -> crawlMenuDatas(id, restaurant)); menuRepository.saveAll(menu); } } catch (Exception err) { System.out.println(err.getMessage()); } }
- 메뉴 정보를 불러오는 것 까지는 완료되지만
List<Menu>
로 불러오면서 Optional 기능이 적용되고 이로인해 만약List<Menu>
가 find 되지 않을시 List 는 empty 값으로 처리되기 때문에 orElse에 거쳐지지 않는다Optional<List<Entity>>
를 적용하고자 한다면 반환값은 List / not List 가 되어야 한다. 하지만 위 경우에는 빈 리스트가 반환되기 때문에 결국 orElse로 걸쳐질 수 없다- 결국 생각해보면 JPA에서 List<?> 로 반환되는 값은 null일 경우가 없기때문에 궂이 Optional로 지정할 필요가 없다. 즉 아래와 같이 바꿔 적용
List<Menu> findMenuByRestaurant(Restaurant restaurant);
Repository Test
@Test
public void findByIdIfNotExistScrap() throws Exception {
// given
List<Menu> allMenus = new ArrayList<>();
List<Long> ids = Arrays.asList(11678758l,
11678838l,
11679306l,
11679353l,
11679393l,11679455l);
try {
for (Long id : ids) {
Restaurant restaurant = restaurantsRepository
.findById(id)
.orElseThrow(() -> new IllegalArgumentException("restaurant cant be null"));
List<Menu> menus = menuRepository
.findMenuByRestaurant(restaurant);
if(menus == null || menus.isEmpty()){
// 리스트가 비어있다면 스크래핑
crawlMenuDatas(id, restaurant);
}
allMenus.addAll(menus);
}
menuRepository.saveAll(allMenus);
} catch (Exception err) {
System.out.println(err.getMessage());
}
}
public List<Menu> crawlMenuDatas(long restaurantId, Restaurant entity) {
List<Menu> menus = new ArrayList<>();
try {
long id = restaurantId;
String url = String.format("/restaurant/%d/menu/list", id);
String _url = HOST_v1 + url;
HttpHeaders httpHeaders = utility.getDefaultHeader();
HttpEntity requestMessage = new HttpEntity(httpHeaders);
ResponseEntity response = null;
// when
response = restTemplate.exchange(
_url,
HttpMethod.GET,
requestMessage,
String.class);
// 음식점 정보들 파싱
Document doc = Jsoup.parse((String) response.getBody());
Element scriptElement = doc.getElementsByTag("script").get(2);
String innerJson = scriptElement.childNode(0).toString();
int start = innerJson.indexOf("window.__APOLLO_STATE__");
int end = innerJson.indexOf("window.__PLACE_STATE__");
// JSON으로 파싱
JSONObject target = new JSONObject(innerJson.substring(start, end).substring(25));
JSONArray jsonArray = target.names();
List<String> restaurantList = new ArrayList<>();
for (int i = 0; i < jsonArray.length(); i++) {
String possible = jsonArray.get(i).toString();
// 레스토랑 정보를 갖고 있는 곳은 RestaurantListSummary:XXXXXX 의 형태를 띄며 한번의 스크래핑에서 50개의 결과값이 나오게 된다
if (possible.contains("Menu")) {
restaurantList.add(possible);
}
}
//
for (String s : restaurantList) {
// 해당 JObject와 Response 객체간의 매핑
MenuResponseDto mapped_data = gson.fromJson(target.get(s).toString(), MenuResponseDto.class);
Menu data = Menu.builder()
.id(mapped_data.getId())
.restaurant(entity)
.description(mapped_data.getDescription())
.name(mapped_data.getName())
.priority(mapped_data.getPriority())
.build();
menus.add(data);
}
}
catch (Exception err){
System.out.println(err.getMessage());
}
return menus;
}
MenuDTO
@Getter
@Setter
@ToString
public class MenuDTO {
private String name;
private int price;
private String[] images;
private String description;
private String id;
private int priority;
public MenuDTO(Menu entity) {
this.name = entity.getName();
this.price = entity.getPrice();
this.images = entity.getImages().toArray(String[]::new);
this.description = entity.getDescription();
this.id = entity.getId();
this.priority = entity.getPriority();
}
}
Service
public List<MenuDTO> findMenusById(long restaurantId) {
List<MenuDTO> result = null;
Restaurant restaurant = restaurantsRepository
.findById(restaurantId)
.orElseThrow(() -> new IllegalArgumentException("잘못된 접근입니다."));
List<Menu> menus = menuRepository
.findMenuByRestaurant(restaurant);
if(menus == null || menus.isEmpty()){
// 리스트가 비어있다면
result = crawlMenuDatas(restaurantId, restaurant);
}
else{
// 만약 리스트가 비어있지 않다면 원래 데이터를 DTO로 변환
result = menus.stream()
.map(menu -> new MenuDTO(menu)).collect(Collectors.toList());
}
return result;
}
Controller
@GetMapping("/{id}")
public String findById(@PathVariable long id, Model model){
List<MenuDTO> menus = restaurantService.findMenusById(id);
model.addAttribute("menus", menus);
return "restaurant-detail";
}
@PathVariable
을 통하여 id 값을 갖고 서비스단에서 비즈니스 로직을 처리합니다View
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<!-- <meta id="_csrf" name="_csrf" th:content="${_csrf.token}"/>-->
<!-- <meta id="_csrf_header" name="_csrf_header" th:content="${_csrf.headerName}"/>-->
<title>Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<h1>메뉴 리스트</h1>
<div class="col-md-12">
<!-- 로그인 기능 영역 -->
<div class="row">
<div class="col-md-6">
<table>
<thead>
<tr>
<th>메뉴명</th>
<th>가격</th>
<!-- <th>이미지</th>-->
<th>설명</th>
</tr>
</thead>
<tbody>
<tr th:each="menuDTO, status:${menus}">
<td th:text="${status.index}"></td>
<!-- <td><img id="imgId" th:src="${restaurantDTO.image_Url}" alt="첨부이미지" /></td>-->
<td th:text="${menuDTO.name}"></td>
<td th:text="${menuDTO.price}"></td>
<!-- <td th:text="${MenuResponseDto.images}"></td>-->
<td th:text="${menuDTO.description}"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
결과 조회