본문 바로가기
Study/Spring Boot

[Spring Boot] JdbcTemplate로 MemoryRepository 대체하기

by DevJaewoo 2022. 2. 14.
반응형

Spring LOGO

Intro

데이터가 메모리에 저장돼 서버를 껐다 켜면 데이터가 날아가는 현상을 방지하기 위해 JdbcTemplate를 사용할 것이다.

JdbcTemplate를 사용하면 기존 JDBC의 반복 코드를 대부분 제거해 SQL과 결과 처리에 집중할 수 있도록 지원해준다.


JDBCTemplate 적용

JdbcTemplate를 사용하기 위해 gradle 파일에 JDBCimplement 해줘야 한다.

 

build.gradle

dependencies {
    ...
    
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    
    ...
}

 

이제 MemberRepository를 구현하는 JdbcTemplateMemberRepository 클래스를 만들고,

JdbcTemplate 변수를 만들어주자.

 

JdbcTemplateMemberRepository.java

package com.devjaewoo.hellospring.repository;

import com.devjaewoo.hellospring.domain.Member;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.util.List;
import java.util.Optional;

public class JdbcTemplateMemberRepository implements MemberRepository {

    private final JdbcTemplate jdbcTemplate;

    public JdbcTemplateMemberRepository(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public Member save(Member member) {
        return null;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.empty();
    }

    @Override
    public Optional<Member> findByName(String name) {
        return Optional.empty();
    }

    @Override
    public List<Member> findAll() {
        return null;
    }
}

 

그런데 JdbcTemplateDataSource를 필요로 한다.

여기서 우리가 이전에 application.yml에 등록한 DataSource가 쓰인다.

spring:
  datasource:
    url: jdbc:h2:~/test
    driver-class-name: org.h2.Driver
    username: sa

 

DataSource는 스프링 컨테이너에서 자동으로 Bean으로 등록시켜주기 때문에, @Autowired로 받기만 하면 된다.

지금 RepositoryBean으로 등록하는 역할을 SpringConfig 클래스에서 해주고 있으니 SpringConfig의 생성자에 DataSource를 받도록 수정하자.

 

하는 김에 memberRepository 함수도 MemoryMemberRepository가 아닌

JdbcTemplateMemberRepository를 반환하도록 수정해주자.

 

SpringConfig.java

package com.devjaewoo.hellospring;

import com.devjaewoo.hellospring.repository.JdbcTemplateMemberRepository;
import com.devjaewoo.hellospring.repository.MemberRepository;
import com.devjaewoo.hellospring.repository.MemoryMemberRepository;
import com.devjaewoo.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class SpringConfig {

    private final DataSource dataSource;

    @Autowired
    public SpringConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        // return new MemoryMemberRepository();
        return new JdbcTemplateMemberRepository(dataSource);
    }
}

 

이제 JdbcTemplateMemberRepository 클래스에서 JdbcTemplate를 쓸 수 있게 되었다. 완성된 코드는 다음과 같다.

 

package com.devjaewoo.hellospring.repository;

import com.devjaewoo.hellospring.domain.Member;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class JdbcTemplateMemberRepository implements MemberRepository {

    private final JdbcTemplate jdbcTemplate;

    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;
        };
    }
}

 

코드를 보면 JdbcTemplate에서 쿼리를 날릴 때 memberRowMapper를 호출해주는 것을 볼 수 있다.

JdbcTemplate는 쿼리의 결과를 ResultSet 형태로 제공하는데, 이 ResultSet을 이용해 내가 받고자 하는 데이터 타입으로 바꿔주는 함수를 만들어야 한다. 

JdbcTemplate에서 이 함수를 받아 쿼리 결과를 List <Member>로 반환해주는 원리이다.

 

위의 코드에선 그 역할을 memberRowMapper 함수가 맡는데, rsrowNum을 받아 member를 반환해주는 것을 볼 수 있다.


기능 동작 확인

기능 동작 확인에 앞서 h2 콘솔로 다시 들어가 테이블을 만들어줘야 한다.

h2 콘솔로 들어가 아래의 SQL을 실행해 테이블을 만들어주자.

 

DROP TABLE IF EXISTS MEMBER;
CREATE TABLE MEMBER (
    ID BIGINT GENERATED BY DEFAULT AS IDENTITY,
    NAME VARCHAR(255),
    PRIMARY KEY(ID)
);

 

이제 localhost:8080으로 들어가서 회원가입을 해보자.

적용 결과

 

회원이 정상적으로 저장되고, 서버를 껐다가 켜도 데이터가 남아있는 것을 확인할 수 있다.

다음 시간에는 통합 테스트를 해보도록 하겠다.


출처

반응형