-
데이터 접근 기술- MemoryBackEnd/Spring DB 2023. 1. 31. 21:00반응형
Overview
이번 포스팅부터는 실무에서 주로 사용하는 다양한 데이터 접근 기술들에 대해 알아보겠습니다. 단순히 메모리에 상품 데이터를 저장하는 프로젝트를 기준으로 실제 데이터 접근 기술들을 하나씩 적용해보고, 각각의 데이터 접근 기술들에 대한 사용법 및 장단점에 대해 알아보겠습니다.
적용 데이터 접근 기술
- JdbcTemplate
- MyBatis
- JPA, Hibernate
- 스프링 데이터 JPA
- Querydsl
프로젝트 설명
전체 소스코드는 github repository를 참고하시면 됩니다.
- 도메인은 Item을 사용하며 이름, 가격, 수량을 속성으로 가지고 있습니다.
- 메모리 구현체에서 향후 다양한 데이터 접근 기술 구현체로 손쉽게 변경하기 위해 리포지토리에 인터페이스를 도입했습니다.
Memory
다음 코드는 ItemRepository 인터페이스를 구현한 메모리 저장소입니다. 메모리이기 때문에 다시 실행하면 기존에 저장된 데이터는 모두 사라집니다.
package hello.itemservice.repository.memory; import hello.itemservice.domain.Item; import hello.itemservice.repository.ItemRepository; import hello.itemservice.repository.ItemSearchCond; import hello.itemservice.repository.ItemUpdateDto; import org.springframework.stereotype.Repository; import org.springframework.util.ObjectUtils; import java.util.*; import java.util.stream.Collectors; @Repository public class MemoryItemRepository implements ItemRepository { private static final Map<Long, Item> store = new HashMap<>(); //static private static long sequence = 0L; //static @Override public Item save(Item item) { item.setId(++sequence); store.put(item.getId(), item); return item; } @Override public void update(Long itemId, ItemUpdateDto updateParam) { Item findItem = findById(itemId).orElseThrow(); findItem.setItemName(updateParam.getItemName()); findItem.setPrice(updateParam.getPrice()); findItem.setQuantity(updateParam.getQuantity()); } @Override public Optional<Item> findById(Long id) { return Optional.ofNullable(store.get(id)); } @Override public List<Item> findAll(ItemSearchCond cond) { String itemName = cond.getItemName(); Integer maxPrice = cond.getMaxPrice(); return store.values().stream() .filter(item -> { if (ObjectUtils.isEmpty(itemName)) { return true; } return item.getItemName().contains(itemName); }).filter(item -> { if (maxPrice == null) { return true; } return item.getPrice() <= maxPrice; }) .collect(Collectors.toList()); } public void clearStore() { store.clear(); } }
다음은 MemoryConfig 설정 파일입니다. ItemServiceV1, MemoryItemRepository를 스프링 빈으로 등록하고 생성자를 통해 의존관계를 주입합니다. 이렇게 수동으로 빈을 등록한 이유는 서비스와 리포지토리를 편리하게 변경하기 위함입니다. 컨트롤러는 컴포넌트 스캔을 사용합니다(ItemServiceApplication에서 애노테이션으로 설정합니다.).
package hello.itemservice.config; import hello.itemservice.repository.ItemRepository; import hello.itemservice.repository.memory.MemoryItemRepository; import hello.itemservice.service.ItemService; import hello.itemservice.service.ItemServiceV1; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MemoryConfig { @Bean public ItemService itemService() { return new ItemServiceV1(itemRepository()); } @Bean public ItemRepository itemRepository() { return new MemoryItemRepository(); } }
메모리이기에 서버를 내리면 기존 데이터가 사라집니다. 다음과 같이 애플리케이션을 실행할 때 초기 데이터를 저장할 수 있습니다.
package hello.itemservice; import hello.itemservice.domain.Item; import hello.itemservice.repository.ItemRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; @Slf4j @RequiredArgsConstructor public class TestDataInit { private final ItemRepository itemRepository; /** * 확인용 초기 데이터 추가 */ @EventListener(ApplicationReadyEvent.class) public void initData() { log.info("test data init"); itemRepository.save(new Item("itemA", 10000, 10)); itemRepository.save(new Item("itemB", 20000, 20)); } }
- @EventListener(ApplicationReadyEvent.class): 스프링 컨테이너가 초기화를 완전히 끝내고 실행 준비가 되었을 때 발생하는 이벤트입니다. 스프링이 이 시점에 애노테이션이 붙은 initData() 메서드를 호출합니다.
Note. @PostConstruct를 사용할 경우, AOP 같은 부분이 다 처리되지 않은 시점에 호출될 수 있기에 간혹 문제가 발생할 수 있습니다. 예를 들어, @Transactional과 같은 AOP가 적용되지 않은 상태로 호출될 수 있습니다.
ItemServiceApplication
package hello.itemservice; import hello.itemservice.config.*; import hello.itemservice.repository.ItemRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; @Slf4j @Import(MemoryConfig.class) @SpringBootApplication(scanBasePackages = "hello.itemservice.web") public class ItemServiceApplication { public static void main(String[] args) { SpringApplication.run(ItemServiceApplication.class, args); } @Bean @Profile("local") public TestDataInit testDataInit(ItemRepository itemRepository) { return new TestDataInit(itemRepository); } }
- @Import(MemoryConfig.class): 앞서 작성한 MemoryConfig 설정 파일을 사용합니다.
- scanBasePackages = "hello.itemservice.web": 해당 경로 하위(컨트롤러)만 컴포넌트 스캔을 사용하고, 나머지는 직접 수동으로 등록합니다.
- @Profile("local"): local 프로필의 경우만 testDataInit 빈을 등록합니다.
Profile
스프링은 로딩 시점에 application.properties의 spring.profiles.active 속성을 읽어 프로필로 사용합니다. 로컬, 개발 환경, 운영 환경 등 다양한 환경에서 다른 설정을 할 때 사용하는 정보입니다.
# application.properties spring.profiles.active=local
이후 포스팅에서 사용할 데이터베이스 테이블입니다.
drop table if exists item CASCADE; create table item ( id bigint generated by default as identity, item_name varchar(10), price integer, quantity integer, primary key (id) );
[참고 정보]
반응형'BackEnd > Spring DB' 카테고리의 다른 글
데이터 접근 기술- MyBatis (0) 2023.02.02 데이터 접근 기술- 스프링 JdbcTemplate (0) 2023.02.01 JdbcTemplate (0) 2023.01.28 예외(Exception) (2) (0) 2023.01.28 예외(Exception) (1) (0) 2023.01.28