데이터 접근 기술은 다양하다. 데이터베이스 접근 방식을 파악하기 위해 사용했던 JDBC기술과 더불어 아래와 같은 기술들이 있다.
- JDBCTemplate
- MyBatis
- JPA, Hibernate
- 스프링 데이터 JPA
- QueryDSL
이것들을 크게 SQLMapper, ORM 관련 기술 이 2가지 분류로 나눌 수 있다.
SQLMapper
- JDBCTemplate
- MyBatis
개발자가 SQL만 작성하면 해당 SQL 결과를 객체로 편리하게 매핑해준다. JDBC를 직접 사용할 때 발생하는 여러가지 중복을 제거해주고, 개발자에게 편리한 여러 기타 기능을 제공한다.
ORM 관련 기술
JPA. (recommtoon 일부 사용)
JdbcTemplate나 MyBatis 같은 SQL 매퍼 기술은 SQL을 직접 작성해야 하는데, JPA를 사용하면 SQL은 JPA가 대신 작성하고 처리해준다.
- JPA
- JPA를 사용하면 개발자는 저장하고 싶은 객체를 마치 자바 컬렉션에 저장하고 조회하듯 사용하면 ORM 기술이 데이터베이스에 해당 객체를 저장하고 조회해준다.
- Hibernate
- JPA는 자바 진영의 ORM 표준이고, Hibernate는 JPA에서 가장 많이 사용하는 구현체이다. 자바에서 ORM을 사용할 때는 JPA 인터페이스를 사용하고, 그 구현체로 하이버네이트를 사용한다고 생각하면 된다.
- 스프링 데이터 JPA와 Querydsl
- JPA를 더 편리하게 사용할 수 있게 도와주는 프로젝트이다. 실무에서는 JPA를 사용하면 이 프로젝트도 반드시 함께 사용하는 것이 좋다. 필수라고 봐도 되는 수준이다.
데이터베이스 활용 카테고리에서는 위의 데이터 저장 기술들의 핵심기술을 파악하며 이 기술들이 왜 필요하고, 각각의 장단점은 무엇인지 알아본다.
학습 프로젝트 구조
데이터접근 기술을 알아보기 위한 프로젝트의 구조를 설명한다. 데이터베이스 활용 카테고리의 내용은 이 프로젝트를 기반으로 설명한다.
ItemRepository가 인터페이스로 되어있고, 각 데이터베이스 접근 기술들로 구현체를 만들어 리포지토리를 갈아 끼운다.
프로젝트의 기능은 상품을 등록/수정하고, 검색할 수 있는 구조이다.
검색은 상품명을 입력하면 해당 키워드가 포함된 상품명의 상품들만 노출이되고, 가격을 입력하면 해당 가격 이하의 상품들만 노출된다.
상품 등록에서 제한 조건은 없고, ID는 자동으로 1씩 증가한다.
코드로 구조를 보고싶다면 깃허브 링크 로 가면 된다.
참고 - DTO
Data Transfer Object
- 데이터 전송 객체
- DTO는 기능은 없고 데이터를 전달만 하는 용도로 사용되는 객체를 뜻한다.
- 꼭 기능이 없어야 하는 것은 아니다. 객체의 주 목적이 데이터를 전송하는 것이라면 DTO라 할 수 있다.
- 객체 이름에 꼭 DTO를 붙여야 하는 것은 아니다. 다만 붙여두면 용도를 쉽게 알 수 있다.
- 이러한 규칙은 따로 정해진 것이 아니기 때문에 개인 프로젝트라면 일관성 있게 규칙을 정하면 되고, 실무라면 팀이 정하는 방향대로 가면 된다.
해당 프로젝트에서는 아이템을 수정할 때 파라미터를 받는 용도로 사용된다.
(Item 객체를 사용했을 경우 파라미터가 매칭되지 않기 때문. - id는 수정대상이 아님. 등록 메서드는 Item 객체 사용.)
참고
TestDataInit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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)
- 스프링 컨테이너가 완전히 초기화를 끝내고 실행 준비가 되었을 때 발생하는 이벤트이다. 스프링이 이 시점에 해당 애노테이션이 붙은 메서드를 호출해준다.
- 이 기능 대신 @PostConstruct를 사용할 경우 AOP 같은 부분이 다 처리되지 않은 시점에 호출될 수 있어 간혹 문제가 발생할 수 있다.
- 이 기능은 AOP를 포함한 스프링 컨테이너가 완전히 초기화 된 이후 호출되어 이러한 문제가 발생하지 않는다.
ItemServiceApplication
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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를 설정 파일로 사용한다.
- MemoryConfig는 설정용 클래스인데, 서비스와 리포지토리의 구현체를 설정하는 데 사용한다. (편리한 학습을 위함)
- scanBasePackages = “hello.itemservice.web”
- 컴포넌트 스캔의 범위를 지정한다.
- 해당 프로젝트에서 컴포넌트 스캔을 컨트롤러만 사용하기 때문에 범위를 지정해주었다.
- @Profile(“local”)
- 특정 프로필의 경우에만 해당 스프링 빈을 등록한다.
프로필
스프링은 로딩 시점에 application.properties의 spring.profiles.active 속성을 읽어 프로필로 사용한다.
프로필은 로컬, 운영 환경, 테스트 실행 등 다양한 환경에 따라 다른 설정을 할 때 사용하는 정보이다.
예를 들면 로컬PC에서는 로컬 PC에 설치된 데이터베이스에 접근해야 하고, 운영 환경에서는 운영 데이터베이스에 접근해야 한다면 서로 설정 정보가 달라야 한다.
프로필을 사용하면 이런 문제를 깔끔하게 해결할 수 있다.
(recommtoon 에서 겪었던 문제인 듯 - db 접근)
1
spring.profiles.active=local
- 이 위치의 application.properties는 /src/main 하위의 자바 객체를 실행할 때 동작하는 스프링 설정이다. 스프링은 local이라는 프로필로 동작하게 된다.
- 프로필을 지정하지 않으면 default 프로필이 생성된다.
Test 프로필
1
spring.profiles.active=test
- 주로 테스트 케이스를 실행할 때 동작한다.
- 프로젝트 코드에서는 이렇게 설정했을 경우 testDataInit 이라는 스프링 빈이 등록되지 않아 초기 데이터를 추가하지 않는다.
DB 테이블 세팅
1
2
3
4
5
6
7
8
9
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)
);
- generated by default as identity
- 기본 키 생성을 데이터베이스에 위임하는 방법.
- MySQL의 Auto Increment와 같다.
PK는 id이다.
참고
데이터베이스의 기본 키는 아래 3가지 조건을 모두 만족해야 한다.
- null 값은 허용하지 않는다.
- 유일해야 한다.
- 변해서는 안된다.
테이블의 기본 키를 선택하는 전략은 크게 2가지가 있다.
- 자연 키
- 비즈니스에 의미가 있는 키
- ex) 주민등록번호, 이메일, 전화번호
- 대리 키
- 비즈니스와 관련 없는 임의로 만들어진 키, 대체 키로도 불린다.
- ex) 오라클 시퀀스, auto_increment, identity, 키생성 테이블 사용
자연 키보다는 대리키를 권장한다.
자연 키와 대리 키는 각각 장단이 있지만 대리 키의 사용을 권장한다. 예를 들어 자연키인 전화번호를 기본 키로 선택한다면 그 번호가 유일할 수는 있지만 전화번호가 없을 수도 있고 전화번호가 변경될 수도 있다. 따라서 기본 키로 적당하지 않다.
문제는 주민등록번호처럼 그럴듯해 보이는 값이다. 이 값은 null도 아니고, 유일하며 변하지 않는 3가지 조건을 만족하는 듯 하다. 하지만 현실과 비즈니스 규칙은 생각보다 쉽게 변한다. 주민등록번호 조차 여러 가지 이유로 변경될 수 있다.
비즈니스 환경은 언젠가 변한다.
회원 테이블에 주민등록번호가 기본키로 잡혀있었다고 하자. 회원과 관련된 수많은 테이블에서 조인을 위해 주민등록번호를 외래 키로 가지고 있었고 심지어 자식 테이블의 자식테이블까지 이 주민번호가 내려가 있다.
그런데 정부 정책이 변경되면서 법적으로 주민번호를 저장할 수 없게 되면 데이터베이스 테이블과 함께 수많은 애플리케이션 로직을 수정해야 한다.
기본 키의 조건을 현재는 물론이고 미래까지 충족하는 자연 키를 찾기 쉽지 않다. 따라서 대리 키를 기본으로 사용하되 주민번호나 이메일처럼 자연 키의 후보가 되는 컬럼들은 필요에 따라 유니크 인덱스를 설정해 사용하는 것을 권장한다.
JPA는 모든 엔티티에 일관된 방식으로 대리 키 사용을 권장한다.