일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- HTTP
- 코딩테스트
- brute force
- 문자열
- 프로그래머스
- Spring
- 소수
- API
- 라이브템플릿
- 2981
- 최단경로
- 탐욕법
- Greedy
- springboot
- algorithm
- java
- 백준
- beandefinitionstoreexception
- spring security
- counting elements
- error
- codility
- Python
- 알고리즘
- 파이썬
- Dijkstra
- applicationeventpublisher
- BFS
- javascript
- 2018 KAKAO BLIND RECRUITMENT
- Today
- Total
Altiora Petamus
in-memory DB 구현 본문
어떤 데이터베이스를 사용할지 정해지지 않은 상황에서 당장 Rest api 를 만들기 시작해야한다면 어떤 방법을 사용할 수 있을까요?
간단한 CRUD는 in-memory DB 를 구현하여 작성해놓으면 추후 DB가 정해졌을 경우 바로 교체하여 사용할 수 있습니다. 사실 테스트 용도로 자주 사용되는 H2 데이터베이스를 사용해도되지만, 직접 구현하며 흐름을 알아보도록 하겠습니다.
우선 lombok, Spring Web 정로만 선택하여 gradle 프로젝트를 생성해주겠습니다.
db package 아래에 MemoryDbRepository interface 를 만들어줍니다.
public interface MemoryDbRepositoryIfs<T> {
// Create
T save(T entity);
// Read
Optional<T> findById(Long index);
// Delete
void deleteById(Long index);
List<T> findAll();
}
인터페이스에는 간단한 create, read, delete 메서드를 정의해주고, Generic 으로 작성하여 다양한 Entity 들을 사용할 수 있도록 합니다. update 가 없는 이유는 save 를 처리하는 과정에서 update가 이루어질 것이기 때문에 뒤에 따로 작성하도록 하겠습니다.
이를 구현할 Abstract Class 를 만들어주겠습니다.
public abstract class MemoryDbRepository<T> implements MemoryDbRepositoryIfs<T> {
private final List<T> db = new ArrayList<>();
@Override
public T save(T entity) {
return null;
}
@Override
public Optional<T> findById(Long index) {
return Optional.empty();
}
@Override
public void deleteById(Long index) {
}
@Override
public List<T> findAll() {
return db;
}
}
ArrayList 자료구조를 사용해서 DB처럼 동작할 수 있도록 구현합니다.
이제 모든 Entity 들의 공통적으로 가지고 있어야하는 index 를 멤버변수로 갖고 있는 MemoryDbEntity
를 작성해줍니다. 이 클래스는 상속을 활용하여 제네릭의 와일드카드를 제한해주는 용도로 사용합니다.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemoryDbEntity {
private Long index;
}
prime key 를 흉내내는 index 를 MemoryDbEntity
에 만들어줍니다. 모든 Entity 들은 이 MemoryDbEntity
를 상속하게될 것 입니다.
public abstract class MemoryDbRepository<T extends MemoryDbEntity> implements
MemoryDbRepositoryIfs<T> {
private final List<T> db = new ArrayList<>();
private Long index = 0L;
@Override
public T save(T entity) {
var optionalEntity = db.stream()
.filter(i -> i.getIndex().equals(entity.getIndex())).findFirst();
// data 가 이미 존재하는 경우 = Update
if (optionalEntity.isPresent()) {
Long preIndex = optionalEntity.get().getIndex();
entity.setIndex(preIndex);
deleteById(preIndex);
} else {
index++;
entity.setIndex(this.index);
}
db.add(entity);
return entity;
}
@Override
public Optional<T> findById(Long index) {
return db.stream()
.filter(i -> i.getIndex().equals(index)).findFirst();
}
@Override
public void deleteById(Long index) {
var optionalEntity = db.stream()
.filter(i -> i.getIndex().equals(index)).findFirst();
optionalEntity.ifPresent(db::remove);
}
@Override
public List<T> findAll() {
return db;
}
}
<T extends MemoryDbEntity>
에 의해서 MemoryDbEntity
를 반드시 상속받아야만 이 추상클래스를 상속하는게 가능해집니다. 이로 인해서 람다식 안의 getIndex()
호출이 가능해집니다. i
가 MemoryDbEntity
를 상속하고 있다는 보증이 있기 때문입니다.
이미 entity 가 db 내부에 존재하는 경우에는 update 를 위한 코드를 만들어줍니다. index 를 증가시키지 않고 다시 설정하여 값만 변경해주는 방식으로 구현했습니다.
이제 간단한 entity 를 생성하여 기능이 잘 동작하는지 테스트해보겠습니다.
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Book extends MemoryDbEntity {
private String author;
private int price;
}
작가와 가격정보 정도만 들고 있는 Book Class 를 만들고 MemoryDbEntity
를 상속합니다.
이제 BookRepository
를 작성해줍니다.
package com.example.memorydb.repository;
import com.example.memorydb.db.MemoryDbRepository;
import com.example.memorydb.domain.Book;
import org.springframework.stereotype.Repository;
@Repository
public class BookRepository extends MemoryDbRepository<Book> {
}
마치 JPA 를 사용하듯이 작성할 수 있습니다. Book class 가 MemoryDbEntity
를 상속했기 때문에 제네릭의 와일드카드로 사용이 가능한 것을 볼 수 있습니다.
Test 작성
이제 테스트를 작성하여 기능이 제대로 동작하는지 확인해보겠습니다.
하지만 CRUD 테스트의 경우, 각 테스트가 먼저 실행된 테스트의 영향을 받아서 잘못된 결과를 출력하게 될 수 있기 때문에 rollback 과정이 필요합니다. rollback 을 구현할 수 있도록 메서드를 하나 만들어주고 테스트 코드를 작성해줍니다.
package com.example.memorydb.db;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public abstract class MemoryDbRepository<T extends MemoryDbEntity> implements
MemoryDbRepositoryIfs<T> {
private final List<T> db = new ArrayList<>();
private Long index = 0L;
...
// rollback
public void clear() {
this.index = 0L;
db.clear();
}
}
package com.example.memorydb.repository;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.example.memorydb.domain.Book;
import java.util.Optional;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class BookRepositoryTest {
@Autowired
private BookRepository bookRepository;
@AfterEach
void rollback() {
bookRepository.clear();
}
@DisplayName("1. Create")
@Test
void test_1() throws Exception {
Book book = createBook();
bookRepository.save(book);
assertEquals(1, bookRepository.findAll().size());
}
@DisplayName("2. Read")
@Test
void test_2() throws Exception {
Book book = createBook();
bookRepository.save(book);
Optional<Book> findBook = bookRepository.findById(book.getIndex());
assertTrue(findBook.isPresent());
assertEquals(1, book.getIndex());
assertEquals(1, bookRepository.findAll().size());
}
@DisplayName("3. Update")
@Test
void test_3() throws Exception {
Book book = createBook();
Book expected = bookRepository.save(book);
expected.setPrice(30000);
Book updateBook = bookRepository.save(expected);
assertEquals(1, bookRepository.findAll().size());
assertEquals(30000, updateBook.getPrice());
}
@DisplayName("4. Delete")
@Test
void test_4() throws Exception {
Book book = createBook();
Book saveBook = bookRepository.save(book);
bookRepository.deleteById(saveBook.getIndex());
assertTrue(bookRepository.findAll().isEmpty());
assertEquals(0, bookRepository.findAll().size());
}
private Book createBook() {
Book book = new Book();
book.setAuthor("haril");
book.setPrice(17000);
return book;
}
}
테스트가 잘 작동하는 것을 확인할 수 있습니다. 이제 이 메모리 db 로 백엔드를 만들다가 서비스에 사용할 DB 가 정해졌을 때 바로 전환하여 사용하면 됩니다.
최종 프로젝트 구조
'Java > Spring Framework' 카테고리의 다른 글
다양한 HTTP Mapping 2 (0) | 2021.07.31 |
---|---|
다양한 HTTP Mapping 1 (0) | 2021.07.28 |
Spring Security 사용하기 1 (0) | 2021.06.26 |
Spring Security란? (0) | 2021.06.19 |
[Spring] AOP 활용하기 (0) | 2021.05.20 |