Spring MVC - Spring Data JDBC

김소희·2023년 4월 19일
1

Spring Data JDBC 기반의 데이터 액세스 계층을 연동하기 위해 제일 먼저 해야 될 일은 바로 데이터베이스의 테이블도메인 엔티티 클래스설계이다.

✔ 의존 라이브러리 추가
(1) Spring Data JDBC를 사용하기 위해서는 Spring Boot Starter를 추가해야 한다.
(2) 인메모리(In-memory) DB인 H2를 사용하기 위한 의존 라이브러리도 추가했다.

dependencies {
	...
	...
	implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' //(1)
    runtimeOnly 'com.h2database:h2' //(2)
}

Spring에서는 application.properties 또는 application.yml 파일을 통해 Spring에서 사용하는 다양한 설정 정보들을 입력할 수 있다. H2관련 설정을 추가한다.

  • schema-locations: classpath*:db/h2/schema.sql // 테이블 생성 파일 경로설정

‘schema.sql’ 파일은 ‘src/main/resources/db/h2’ 디렉토리 내에 위치한다.

CREATE TABLE IF NOT EXISTS MESSAGE (
    message_id bigint NOT NULL AUTO_INCREMENT,
    message varchar(100) NOT NULL,
    PRIMARY KEY (message_id)
);

이제 프로그램을 실행시키면 로그에서 히카리풀 로그를 볼 수 있다.

HikariPool-1 - Starting... 
HikariPool-1 - Start completed.
H2 console available at '/h2-console'.
Database available at 'jdbc:h2:mem:26d0d5d3-dcef-47f8-8e6b-67898bdcfbd0'

도메인

도메인이란 특정 분야 또는 문제 영역을 가리키는 개념이며, 소프트웨어 분야에서는 소프트웨어 시스템이 다루는 비즈니스적인 업무 영역을 의미한다. 예를 들어, 은행 소프트웨어의 도메인은 금융이고, 의료 소프트웨어의 도메인은 의학이다.

배달 주문 앱을 만들어야 한다면 고객이 음식을 주문하는 과정, 주문받은 음식을 처리하는 과정, 조리된 음식을 배달하는 과정 등의 도메인 지식(Domain Knowledge)들을 서비스 계층에서 비즈니스 로직으로 구현해야 하는 것이다.

도메인 주도 설계 DDD

DDD(Domain Driven Design)는 소프트웨어 시스템을 구축할 때 비즈니스 도메인에 초점을 맞추어 설계하는 방식이다.

엔티티는 도메인 주도 설계(Domain-Driven Design)에서 핵심적인 개념으로 사용되며, 도메인 모델링에서 엔티티를 중심으로 모델링하는 것이 중요하다. 엔티티를 잘 설계하면, 소프트웨어 시스템이 도메인의 복잡성을 잘 다룰 수 있으며, 유지보수와 확장이 쉬워진다.

인터넷 쇼핑몰을 만든다고 가정하면, 상품, 주문, 회원 등이 도메인에 해당한다. DDD에서는 도메인을 중심으로 모델을 만들고, 비즈니스 도메인에서 발생하는 문제를 해결하기 위한 로직을 구현한다. 이렇게 도메인 중심으로 설계를 하면, 개발자는 비즈니스 도메인을 잘 이해하고 있는 상태에서 코드를 작성할 수 있기 때문에, 코드의 유지보수성과 확장성이 높아진다.

애그리거트(Aggregate)


이미지 출처 : https://devlos.tistory.com/51

애그리거트(Aggregate)란 비슷한 업무 도메인들의 묶음을 말한다.
위 그림에서는 상품, 주문, 리뷰, 결제, 회원 5개의 애그리커트가 있으며, 애그리거트안에는 도메인이 모여 있다.

애그리거트를 대표하는 도메인을 DDD에서는 애그리거트 루트(Aggregate Root)라고 한다.
상품 애그리거트의 경우, ‘상품 카테고리’를 알려면 '상품’를 알아야 한다. 따라서 '상품’ 도메인이 애그리거트 루트가 된다.
데이터베이스의 테이블 간 관계로 보자면, 애그리거트 루트는 부모 테이블이 되고, 애그리거트 루트가 아닌 다른 도메인들은 자식 테이블이 되는 셈이다. 따라서 애그리거트 루트(Aggregate Root)의 기본키 정보를 다른 도메인들이 외래키 형태로 가지고 있다고 볼 수 있다.

그리고 애그리거트 내의 도메인들 중에서 다른 모든 도메인들과 직간접적으로 연관이 되어 있는 도메인들을 발견할 수 있다.
따라서 애그리거트 루트는 상품, 주문, 리뷰, 결제정보, 회원이다.

ORM(Object-Relational Mapping)은 말 그대로 객체와 테이블을 매핑하는 기술이기 때문에 앞에서 했던 것처럼 클래스 간의 연관 관계를 찾을 수 밖에 없다.
애그리거트 루트(Aggregate Root)는 Spring Data JDBC가 DDD와 밀접한 관련이 있기 때문에 Spring Data JDBC 기술을 사용하기 위해서는 도메인 모델을 잘 정의하고 애그리거트 루트(Aggregate Root)를 찾아야 한다.


Spring JDBC 템플릿 라이브러리로 만든 Repository 코드

20년 전의 선배 개발자들이 사용하던 순수 JDBC로 만든 레포지토리 코드는 아주 어마어마하게 길다.
나의 정신 건강을 생각해서 코드를 구경하기만 했었다.

그것을 발전시켜 JdbcTemplate을 사용한 코드는 훨씬 간략해져서 현재의 실무에서도 사용된다.
디자인 패턴 중 템플릿 메소드 패턴이라는 것이 있는데, 그것으로 많은 문제를 해결한 결과이다.
자세한 사용법은 JdbcTemplate 메뉴얼을 검색하면 나오며, 아래의 코드로 간단하게 감을 잡아보자.

코드를 살펴보면 스프링이 데이터 소스를 자동으로 인젝션해 주고 있다.
쿼리를 날리고, 결과는 하단에 작성한 람다식 로우 매핑 메소드를 사용해 두 번째 파라미터에 매핑하고 있다.

import java.util.Optional;
import java.util.List; 
import java.util.Map;
import java.util.HashMap;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;

public class JdbcTemplateMemberRepository implements MemberRepository {

	private final JdbcTemplate jdbcTemplate;
    
    @Autowired //생성자가 하나라서 생략가능함
    public JdbcTemplateMemberRepository(DataSource dataSource) {
    	jdbcTemplate = new JdbcTemplate(dataSource);
    }
    
    @Override
    public Member save(Member member) {
    	SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
        
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("name", member.getName());
        
        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
        member.setId(key.longValue());
    	return member;
    }
    
    @Override
    public Optional<Member> findById(Long id) {
    	List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(), id);
        return result.stream().findAny();
    }
    
    @Override
    public Optional<Member> findByName(String name) {
    	List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
        return result.stream().findAny();
    }
    
    @Override
    public List<Member> findAll() {
    	return jdbcTemplate.query("select * from member", memberRowMapper());
    }
    
    private RowMapper<Member> memberRowMapper() {
    	return (rs, rowNum) -> {
        	Member member = new Member();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            return member;
        };
    }     
  
  
}
profile
자바, 코틀린, go, 스프링, SQL 백엔드 개발자 소희의 노트

0개의 댓글