JPA가 없는 IN-MEMORY DB구현
MemoryDbRepositoryInterface
package com.example.restaurant.db;
import java.util.List;
import java.util.Optional;
public interface MemoryDbRepositoryIfs<T> {
Optional<T> findById(int index);
T save(T entity);
void deleteById(int index);
List<T> findAll();
}
MemoryDbRepositoryAbstract
package com.example.restaurant.db;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
abstract public class MemoryDbRepositoryAbstract<T extends MemoryDbEntity> implements MemoryDbRepositoryIfs<T> {
private final List<T> db = new ArrayList<>();
private int index = 0;
@Override
public Optional<T> findById(int index) {
return db.stream()
.filter(it -> it.getIndex() == index)
.findFirst();
}
@Override
public T save(T entity) {
var optionalEntity = db.stream()
.filter(it -> it.getIndex() == entity.getIndex())
.findFirst();
if(optionalEntity.isEmpty()){
index ++;
entity.setIndex(index);
db.add(entity);
return entity;
}else{
var preIndex = optionalEntity
.get()
.getIndex();
entity.setIndex(preIndex);
deleteById(preIndex);
db.add(entity);
return entity;
}
}
@Override
public void deleteById(int index) {
var optionalEntity = db.stream()
.filter(it -> it.getIndex() == index)
.findFirst();
if(optionalEntity.isPresent()){
db.remove(optionalEntity.get());
}
}
@Override
public List<T> findAll() {
return db;
}
}
MemoryDbEntity
package com.example.restaurant.db;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MemoryDbEntity {
protected Integer index;
}
Naver api - Client
searchImageRequest
package com.example.restaurant.naver.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SearchImageReq {
private String query = "";
private int display = 1;
private int start = 1;
private String sort = "sim";
private String filter = "all";
public MultiValueMap<String,String> toMultiValueMap(){
var map = new LinkedMultiValueMap<String,String>();
map.add("query",query);
map.add("display", String.valueOf(display));
map.add("start", String.valueOf(start));
map.add("sort",sort);
map.add("filter",filter);
return map;
}
}
searchImageResponse
package com.example.restaurant.naver.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SearchImageRes {
private String lastBuildDate;
private int total;
private int start;
private int display;
private List<SearchImageItem> items;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class SearchImageItem {
private String title;
private String link;
private String thumbnail;
private String sizeheight;
private String sizewidth;
}
}
searchLocalRequest
package com.example.restaurant.naver.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SearchLocalReq {
private String query = "";
private int display = 1;
private int start = 1;
private String sort = "comment";
public MultiValueMap<String,String> toMultiValueMap(){
var map = new LinkedMultiValueMap<String,String>();
map.add("query",query);
map.add("display", String.valueOf(display));
map.add("start", String.valueOf(start));
map.add("sort",sort);
return map;
}
}
searchLocalResponse
package com.example.restaurant.naver.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SearchLocalRes {
private String lastBuildDate;
private int total;
private int start;
private int display;
private List<SearchLocalItem> items;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class SearchLocalItem {
private String title;
private String link;
private String description;
private String category;
private String telephone;
private String address;
private String roadAddress;
private int mapx;
private int mapy;
}
}
Naver - Client
NaverClient
package com.example.restaurant.naver;
import com.example.restaurant.naver.dto.SearchImageReq;
import com.example.restaurant.naver.dto.SearchImageRes;
import com.example.restaurant.naver.dto.SearchLocalReq;
import com.example.restaurant.naver.dto.SearchLocalRes;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@Component
public class NaverClient {
@Value("${naver.client.id}")
private String naverClientId;
@Value("${naver.client.secret}")
private String naverClientSecret;
@Value("${naver.url.search.local}")
private String naverLocalSearchUrl;
@Value("${naver.url.search.image}")
private String naverImageSearchUrl;
// -- search local -- //
public SearchLocalRes searchLocal(SearchLocalReq searchLocalReq){
var uri = UriComponentsBuilder
.fromUriString(naverLocalSearchUrl)
.queryParams(searchLocalReq.toMultiValueMap())
.build()
.encode()
.toUri();
var headers = new HttpHeaders();
headers.set("X-Naver-Client-Id",naverClientId);
headers.set("X-Naver-Client-Secret",naverClientSecret);
headers.setContentType(MediaType.APPLICATION_JSON);
var httpEntity = new HttpEntity<>(headers);
var responseType = new ParameterizedTypeReference<SearchLocalRes>(){};
var responseEntity = new RestTemplate()
.exchange(
uri,
HttpMethod.GET,
httpEntity,
responseType
);
return responseEntity.getBody();
}
// -- search Image -- //
public SearchImageRes searchImage(SearchImageReq searchImageReq){
var uri = UriComponentsBuilder
.fromUriString(naverImageSearchUrl)
.queryParams(searchImageReq.toMultiValueMap())
.build()
.encode()
.toUri();
var headers = new HttpHeaders();
headers.set("X-Naver-Client-Id",naverClientId);
headers.set("X-Naver-Client-Secret",naverClientSecret);
headers.setContentType(MediaType.APPLICATION_JSON);
var httpEntity = new HttpEntity<>(headers);
var responseType = new ParameterizedTypeReference<SearchImageRes>(){};
var responseEntity = new RestTemplate()
.exchange(
uri,
HttpMethod.GET,
httpEntity,
responseType
);
return responseEntity.getBody();
}
}
WishList
- dto
package com.example.restaurant.wishlist.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class WishListDto{
private Integer index;
private String title; // 음식명, 장소명
private String category; // 카테고리
private String address; // 주소
private String roadAddress; // 도로명 주소
private String homePageLink; // 홈페이지 주소
private String imageLink; // 음식, 가게 이미지 주소
private boolean isVisit; // 방문여부
private int visitCount; // 방문 횟수
private LocalDateTime lastVisitDate; // 마지막 방문 일자.
}
- entity
package com.example.restaurant.wishlist.entity;
import com.example.restaurant.db.MemoryDbEntity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class WishListEntity extends MemoryDbEntity {
private String title; // 음식명, 장소명
private String category; // 카테고리
private String address; // 주소
private String roadAddress; // 도로명 주소
private String homePageLink; // 홈페이지 주소
private String imageLink; // 음식, 가게 이미지 주소
private boolean isVisit; // 방문여부
private int visitCount; // 방문 횟수
private LocalDateTime lastVisitDate; // 마지막 방문 일자.
}
- repository
package com.example.restaurant.wishlist.repository;
import com.example.restaurant.db.MemoryDbRepositoryAbstract;
import com.example.restaurant.wishlist.entity.WishListEntity;
import org.springframework.stereotype.Repository;
@Repository
public class WishListRepository extends MemoryDbRepositoryAbstract<WishListEntity> {}
- service
package com.example.restaurant.wishlist.service;
import com.example.restaurant.naver.NaverClient;
import com.example.restaurant.naver.dto.SearchImageReq;
import com.example.restaurant.naver.dto.SearchLocalReq;
import com.example.restaurant.wishlist.dto.WishListDto;
import com.example.restaurant.wishlist.entity.WishListEntity;
import com.example.restaurant.wishlist.repository.WishListRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class WishListService {
private final NaverClient naverClient;
private final WishListRepository wishListRepository;
public WishListDto search(String query) {
// 지역검색
var searchLocalReq = new SearchLocalReq();
searchLocalReq.setQuery(query);
var searchLocalRes = naverClient.searchLocal(searchLocalReq);
if (searchLocalRes.getTotal() > 0) {
var localItem = searchLocalRes
.getItems()
.stream()
.findFirst()
.get();
var imageQuery = localItem
.getTitle()
.replaceAll("<[^>]*>", ""); // 정규표현식
var searchImageReq = new SearchImageReq();
searchImageReq.setQuery(imageQuery);
// 이미지 검색
var searchImageRes = naverClient.searchImage(searchImageReq);
if (searchLocalRes.getTotal() > 0) {
var rst = new WishListDto();
var imageItem = searchImageRes
.getItems()
.stream()
.findFirst()
.get();
rst.setTitle(localItem.getTitle());
rst.setCategory(localItem.getCategory());
rst.setAddress(localItem.getAddress());
rst.setRoadAddress(localItem.getRoadAddress());
rst.setHomePageLink(localItem.getLink());
rst.setImageLink(imageItem.getLink());
return rst;
}
}
return new WishListDto();
}
public WishListDto add(WishListDto wishlistDto) {
var entity = dtoToEntity(wishlistDto);
var saveEntity = wishListRepository.save(entity);
return entityToDto(saveEntity);
}
private WishListDto entityToDto(WishListEntity wishListEntity) {
var dto = new WishListDto();
dto.setIndex(wishListEntity.getIndex());
dto.setTitle(wishListEntity.getTitle());
dto.setCategory(wishListEntity.getCategory());
dto.setAddress(wishListEntity.getAddress());
dto.setRoadAddress(wishListEntity.getRoadAddress());
dto.setHomePageLink(wishListEntity.getHomePageLink());
dto.setImageLink(wishListEntity.getImageLink());
dto.setVisit(wishListEntity.isVisit());
dto.setVisitCount(wishListEntity.getVisitCount());
dto.setLastVisitDate(wishListEntity.getLastVisitDate());
return dto;
}
private WishListEntity dtoToEntity(WishListDto wishListDto) {
var entity = new WishListEntity();
entity.setIndex(wishListDto.getIndex());
entity.setTitle(wishListDto.getTitle());
entity.setCategory(wishListDto.getCategory());
entity.setAddress(wishListDto.getAddress());
entity.setRoadAddress(wishListDto.getRoadAddress());
entity.setHomePageLink(wishListDto.getHomePageLink());
entity.setImageLink(wishListDto.getImageLink());
entity.setVisit(wishListDto.isVisit());
entity.setVisitCount(wishListDto.getVisitCount());
entity.setLastVisitDate(wishListDto.getLastVisitDate());
return entity;
}
public List<WishListDto> findAll() {
return wishListRepository.findAll()
.stream()
.map((it) ->
entityToDto(it))
.collect(Collectors
.toList());
}
public void delete(int index) {
wishListRepository.deleteById(index);
}
public void addVisit(int index){
var wishItem = wishListRepository.findById(index);
if(wishItem.isPresent()){
var item = wishItem.get();
item.setVisit(true);
item.setVisitCount(item.getVisitCount()+1);
}
}
}
JUnit 단위 테스트
package com.example.restaurant.naver;
import com.example.restaurant.naver.dto.SearchImageReq;
import com.example.restaurant.naver.dto.SearchLocalReq;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class NaverClientTest {
@Autowired
private NaverClient naverClient;
@Test
public void searchLocalTest(){
var search = new SearchLocalReq();
search.setQuery("갈비집");
var rst = naverClient.searchLocal(search);
System.out.println(rst);
Assertions.assertNotNull(rst
.getItems()
.stream()
.findFirst()
.get()
.getCategory());
}
@Test
public void searchImageTest(){
var search = new SearchImageReq();
search.setQuery("갈비집");
var rst = naverClient.searchImage(search);
System.out.println(rst);
}
}
단위 테스트에는 Slf4j를 사용할 수 없다는 사실을 처음 알았다..
통합테스트
package com.example.restaurant.wishlist.repository;
import com.example.restaurant.wishlist.entity.WishListEntity;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class WishListRepositoryTest {
@Autowired
private WishListRepository wishListRepository;
private WishListEntity create(){
var wishList = new WishListEntity();
wishList.setTitle("title");
wishList.setCategory("category");
wishList.setAddress("address");
wishList.setRoadAddress("readAddress");
wishList.setHomePageLink("");
wishList.setImageLink("");
wishList.setVisitCount(0);
wishList.setVisit(false);
wishList.setLastVisitDate(null);
return wishList;
}
@Test
public void saveTest(){
var wishListEntity = create();
var expected = wishListRepository.save(wishListEntity);
Assertions.assertNotNull(expected);
Assertions.assertEquals(1, expected.getIndex());
}
@Test
public void updateTest(){
var wishListEntity = create();
var expected = wishListRepository.save(wishListEntity);
expected.setTitle("update test");
var saveEntity = wishListRepository.save(expected);
Assertions.assertEquals("update test",saveEntity.getTitle());
Assertions.assertEquals(1,wishListRepository
.findAll()
.size());
}
@Test
public void findByIdTest(){
var wishListEntity = create();
wishListRepository.save(wishListEntity);
var expected = wishListRepository.findById(1);
Assertions.assertEquals(true,expected.isPresent());
Assertions.assertEquals(1, expected.get().getIndex());
}
@Test
public void deleteTest(){
var wishListEntity = create();
wishListRepository.save(wishListEntity);
wishListRepository.deleteById(1);
int count = wishListRepository
.findAll()
.size();
Assertions.assertEquals(0,count);
}
@Test
public void listAllTest(){
var wishListEntity1 = create();
wishListRepository.save(wishListEntity1);
var wishListEntity2 = create();
wishListRepository.save(wishListEntity2);
int count = wishListRepository
.findAll()
.size();
Assertions.assertEquals(2,count);
}
}
마지막으로 application.yml
'Spring > Spring Boot' 카테고리의 다른 글
| [게시판 RESTful API] - 프로젝트 세팅 (1) (0) | 2022.08.20 |
|---|---|
| [Spring Boot] - 유효성 검증을 하는 방법과 유효성 검증 실패 시 Exception핸들링을 하는 4가지 방법을 알아보자 (0) | 2022.08.13 |
| [Spring Boot, RIOT API] - Unirest로 RIOT API를 요청하고 ObjectMapper를 배워보자. (0) | 2022.08.10 |
| [Spring Boot] Transactional 어노테이션에 대해 알아보자. (0) | 2022.04.30 |
| [Spring Data Jpa] (JUNIT, DDL) 외래키 제약조건에 NOT NULL을 추가해보자. (0) | 2022.04.23 |