요구사항 분석
- 고객은 원하는 키워드를 검색할 수 있다.
사진, 장소, 카테고리, 주소, 도로명, 홈페이지 링크를 확인할 수 있다.
- 고객은 결과를 위시리스트에 추가할 수 있으며 추가할 경우, 나의 맛집 리스트에 추가된다.
나의 맛집 리스트에서는 방문여부, 마지막 방문일자, 방문횟수를 추가적으로 확인할 수 있다.
나의 맛집 리스트에서는 방문추가와 위시리스트 취소를 할 수 있다.
- 고객이 위시리스트 삭제를 누르면 나의 맛집 리스트에서 삭제된다.
- 고객이 방문추가를 누르면 방문 횟수는 1이 증가한다.
프로젝트 설계
vue.js + boostrap + spring boot + db(h2)
Memory CRUD DB 개발하기
*MemoryDbEntity.java
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MemoryDbEntity {
protected Integer index;
}
* MemoryDbRepositoryIfs.java
public interface MemoryDbRepositoryIfs<T> {
Optional<T> findById(int index);
T save(T entity);
void deleteById(int index);
List<T> listAll();
}
JPA를 사용하기 위해 통상적으로 구현하는 CrudRepository 인터페이스를 단순히 배열을 이용해서 구현한다.
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(optionalEntity);
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> listAll() {
return db;
}
}
stream()을 통해 List<T> db 에 있는 모든 원소들을 순회할 수 있으며, filter를 통해 해당 index의 존재여부를 true, false로 반환한다. 보통 아무것도 없는 null의 경우도 Optional<null>로 리턴되는데 일반적인 null과 다르다. 해당 문법들은 Optional로 리턴형이 Optional이기 때문에 이후 잘 사용을 해야 한다.
if(optionalEntity.isEmpty()); optionalEntiy가 비어있는지 검사하며, 아래에 나오는 if(optionalEntity.isPresent())의 반대의미이다.
var preIndex = optionalEntity().get().getIndex(); 에서 optionalEntity()는 Optional인데, get()으로 강제로 호출할 수 있다. get()을 사용하 수 있었던 이유는 isEmpty()가 아니므로 무조건 null이 아니기 때문에 강제 호출이 가능하다. get() 문법은 안티패턴이므로 orElse 문법을 사용한다.
if(optionalEntity.isPresent())는, Optional 리턴형인 optionalEntity에 null이 아닌 값이 존재하는지 확인하는 과정으로, 실제 값이 존재하는 경우에만 삭제를 하도록 한다. 만약에 index에 해당하는 optionalEntiy가 존재하지 않는다면 삭제하지 않을 것이다. 해당 문법은 안티패턴이므로 isPresent() 대신에 orElse 문법을 사용한다.
@Repository
public class WishListRepository extends MemoryDbRepositoryAbstarct<WishListEntity> {
}
추상클래스로 만든 저장소를 구현하고 @Repository를 붙여주어서 Spring Bean에 등록한다.
이제, WishListRepository는 테스트를 할 수 있다.
@NoArgsConsctructor
@AllArgsConstructor
@Data
public class WishListEntity extends MemoryDbEntity {
private String title;
private String category;
private String address;
private String readAddress;
private String homePageLink;
private String imageLink;
private boolean isVisit;
private int visitCount;
private LocalDateTime lastVisitDate;
}
@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.setReadAddress("readAddress");
wishList.setHomePageList("");
wishList.setImageLink("");
wishList.setVisit(false);
wishList.setVisitCount(0);
wishList.setAstVisitDate(null);
}
@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.listAll().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);
wishListRepisotry.delteById(1);
int count = wishListRepository.listAll().size();
Assertions.assertEquals(0, count);
}
@Test
public void listAllTest() {
var wishListEntity1 = create();
wishListRepository.save(wishListEntity1);
var wishListEntity2 = create();
wishListRepository.save(wishListEntity2);
int count = wishListRepository.listAll().size();
Assertions.assertEquals(2, count);
}
}
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
...
<S extends T> S save(S entity);
Optional<T> findById(ID id);
void deleteById(ID id);
Iterable<T> findAll();
...
}
저장소를 직접 구현했지만, CrudRepository를 상속하면, 위에서 직접 정의한 기능들을 사용할 수 있다. 또한 CrudRepository를 사용하면 저장소는 별도의 테스트를 할 필요도 없어진다.
'Spring' 카테고리의 다른 글
네이버 지역검색 API를 활용한 맛집 List 제작 - (3) (0) | 2021.08.22 |
---|---|
네이버 지역검색 API를 활용한 맛집 List 제작 - (2) (0) | 2021.08.22 |
Swagger (0) | 2021.08.21 |
@Valid를 위한 Blank, Empty, Null 차이 (0) | 2021.05.13 |
Spring Framework란? 기본 핵심 개념 정리 (0) | 2020.11.10 |