CS/김영한 스프링 강의

스프링 DB 2편 - 데이터 접근 핵심 원리 - 섹션1. 데이터 접근 기술 - 시작

용나리 2023. 8. 19. 13:18

섹션 0 내용

 

이하 섹션1

 

앞에서 핵심 원리를 배웠으니 이번엔 진짜 여러 DB를 가지고 사용해본다.

기술을 SQLMapper, ORM 관련 기술로 2가지로 나눌 수 있는데 SQL Mapper는 내가 sql문을 직접 짜면 알아서 변형해줘서 실행하는거라 빠르게 하고 세세한 커스텀으로 jdbc로 했을때 생기는 중복을 방지 가능. ORM 주요 기능은 일반 객체 정의할 때 처럼 정의만 하면 ORM이 알아서 DB에 sql문 쳐줘가지고 일반 자바 객체 다루듯이 할 수 있다. JPA를 사용하면 기본적인 SQL은 JPA가 대신 작성하고 처리해준다. JPA는 자바 진영의 ORM 표준이고 Hibernate가 제일 많이 사용되는 JPA의 구현체이다. JPA를 인터페이스, Hibernate를 실체화된 객체라고 생각하면 된다 함. 여기에 스프링 데이터 JPA와 Querydsl은 현업에서 정말 정말 많이 쓰여서 필수다.

 

앞에서 레퍼지토리를 인터페이스로 만들고 컨버터 이용해서 DB만 갈아끼우면 된다는 걸 알기 때문에 처음에는 메모리부터 시작해서 여러 DB를 적용해볼 거다. 이 섹션1은 셋업 겸 메모리 가지고 하는거.

 

예전에 강의 실습으로 만들었던 상품 등록 사이트에 기능이 조금 추가된 형태다. 이걸 베이스로 하고 레퍼지토리를 바꿔가며 실습할건데, 코드 구조를 간략하게만 보자.

기존의 베이스 서비스 기능들에서 필터기능이 추가된 형태다. 구조에 대해서 말인데

Dto가 붙은건 data transfer object의 줄임말인데 단순히 컨트롤러, 서비스나 레퍼지토리와 서비스 서로간에서 데이터를 주고받기용으로 만든 객체다. 그야 자바는 객체 중심이니까.. 얘를 레퍼지토리 폴더 안에 넣을지 서비스 폴더 안에 넣을지 고민하는 거다.

여기선 레퍼지토리 안에 넣었는데, 결국 레퍼지토리가 가지고 있어서 안을 탐색하고 하니까 레퍼지토리가 소유하는게 맞다고 판단해서 넣은거다. 만약 똑같은 단순 데이터 전달 용도인데 정말 서비스에서만 만들고 사라지는 거라면 서비스 폴더 안에 넣는게 맞다. 이런식으로 어디에 넣을지 판단하는거.

 

 

설정도 보자.

실행할 때 프로필을 "local"로 설정해놔서 메인 함수에 만약 프로필이 "local"일 경우 테스트를 위한 아이템을 미리 만들어놓으라는 식으로 부여할 수 있다. 테스트할 때는 미리 만들어놓지 않게끔 테스트 패키지 밑에 따로 만든 모습이다.

메인에서 컴포넌트 스캔하는 것도 프로젝트 범위 내에서만 스캔하도록 지정한 모습이다.

 

 

테스트도 보자.

@SpringBootTest
class ItemRepositoryTest {

    @Autowired
    ItemRepository itemRepository;

    @AfterEach
    void afterEach() {
        //MemoryItemRepository 의 경우 제한적으로 사용
        if (itemRepository instanceof MemoryItemRepository) {
            ((MemoryItemRepository) itemRepository).clearStore();
        }
    }

    @Test
    void save() {
        //given
        Item item = new Item("itemA", 10000, 10);

        //when
        Item savedItem = itemRepository.save(item);

        //then
        Item findItem = itemRepository.findById(item.getId()).get();
        assertThat(findItem).isEqualTo(savedItem);
    }

    @Test
    void updateItem() {
        //given
        Item item = new Item("item1", 10000, 10);
        Item savedItem = itemRepository.save(item);
        Long itemId = savedItem.getId();

        //when
        ItemUpdateDto updateParam = new ItemUpdateDto("item2", 20000, 30);
        itemRepository.update(itemId, updateParam);

        //then
        Item findItem = itemRepository.findById(itemId).get();
        assertThat(findItem.getItemName()).isEqualTo(updateParam.getItemName());
        assertThat(findItem.getPrice()).isEqualTo(updateParam.getPrice());
        assertThat(findItem.getQuantity()).isEqualTo(updateParam.getQuantity());
    }

    @Test
    void findItems() {
        //given
        Item item1 = new Item("itemA-1", 10000, 10);
        Item item2 = new Item("itemA-2", 20000, 20);
        Item item3 = new Item("itemB-1", 30000, 30);

        itemRepository.save(item1);
        itemRepository.save(item2);
        itemRepository.save(item3);

        //둘 다 없음 검증
        test(null, null, item1, item2, item3);
        test("", null, item1, item2, item3);

        //itemName 검증
        test("itemA", null, item1, item2);
        test("temA", null, item1, item2);
        test("itemB", null, item3);

        //maxPrice 검증
        test(null, 10000, item1);

        //둘 다 있음 검증
        test("itemA", 10000, item1);
    }

    void test(String itemName, Integer maxPrice, Item... items) {
        List<Item> result = itemRepository.findAll(new ItemSearchCond(itemName, maxPrice));
        assertThat(result).containsExactly(items);
    }
}

테스트의 경우 테스트후 db를 다시 원래대로 돌려놓는게 필요한데 db는 트랜잭션 써가지고 할 거라서 메모리일 때만 clear하도록 만들었다. null과 빈 문자 "" 일때도 검증을 하도록 테스트를 해보자. 중요한건 인터페이스를 테스트 한다는 것. 그래서 나중에 인터페이스를 구체화하는 클래스가 다른걸로 바뀌더라도 그것에 대해 테스트할 수 있다.

 

db는 일단 h2 사용할거임. 근데 참고사항.