[Spring 입문] 06.스프링 DB 접근 기술(1)

Jiwoo Jung·2024년 11월 17일
0

김영한 Spring 입문

목록 보기
6/6

H2 데이터베이스 설치
순수 JDBC
스프링 통합 테스트

H2 데이터베이스 설치

1. 다운로드 및 설치

https://www.h2database.com
h2 데이터베이스 버전은 스프링 부트 버전에 맞춘다.

2. h2.bat 실행

h2/bin/h2.bat 실행

3. 데이터베이스 파일 생성

최초 실행 시 데이터베이스 파일을 생성해야 한다.
JDBC URL: jdbc:h2:~/test를 입력하고 연결 클릭
C:\Users\jungtest.mv.db 파일 생성 확인
이후부터는 JDBC URL: jdbc:h2:tcp://localhost/~/test 로 연결한다.
파일에 직접 접근하지 않고 소켓을 통해 접근하는 것.

데이터베이스가 꼬이면 서버 연결을 완전히 끊고, test.mv.db 삭제하고 다시 생성하는 것도 방법이다.

데이터베이스 생성

DDL(Data Definition Language)를 h2 콘솔에 입력하여 데이터베이스를 생성한다.

drop table if exists member CASCADE;
create table member
(
 id bigint generated by default as identity,
 name varchar(255),
 primary key (id)
);
  • spring에서 Long이 sql 문법에서는 bigint
  • generated by default as identity: db가 자동으로 값 채워줌

데이터 삽입

DML(Data Manipulation Language)를 h2 콘솔에 입력하여 데이터베이스에 데이터를 삽입한다.
insert into member(name) values('spring')

  • id는 데이터베이스에서 자동으로 생성해준다.

SQL 폴더

프로젝트 루트에 sql 디렉토리 생성 후,
ddl.sql에 위의 ddl문을 저장해두면 github에 업로드할 수 있고 관리도 편하다.


순수 JDBC

JdbcMemberRepository 작성

package com.jiu.spring_basic.repository;

import com.jiu.spring_basic.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class JdbcMemberRepository implements MemberRepository {
    private final DataSource dataSource;

    public JdbcMemberRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public Member save(Member member) {
        String sql = "insert into member(name) values(?)";

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

            pstmt.setString(1, member.getName());

            pstmt.executeUpdate();
            rs = pstmt.getGeneratedKeys(); // Statement.RETURN_GENERATED_KEYS 와 매칭

            if (rs.next()) {
                member.setId(rs.getLong(1));
            } else {
                throw new SQLException("id 조회 실패");
            }
            return member;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public Optional<Member> findById(Long id) {
        String sql = "select * from member where id = ?";

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);

            pstmt.setLong(1, id);

            rs = pstmt.executeQuery();

            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            } else {
                return Optional.empty();
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public List<Member> findAll() {
        String sql = "select * from member";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();
            List<Member> members = new ArrayList<>();
            while(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                members.add(member);
            }
            return members;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public Optional<Member> findByName(String name) {
        String sql = "select * from member where name = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, name);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            }
            return Optional.empty();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource); // DataSourceUtils를 통해서 getConnection
    }

    private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (pstmt != null) {
                pstmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) {
                close(conn);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void close(Connection conn) throws SQLException {
        DataSourceUtils.releaseConnection(conn, dataSource); // DataSourceUtils를 통해서 releaseConnection
    }
}
  • DataSourceUtils를 통해서 get, release Connection

SpringConfig 변경

@Configuration
public class SpringConfig {

    private DataSource dataSource;

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

    @Bean
    public MemberRepository memberRepository() {
//        return new MemoryMemberRepository();
        return new JdbcMemberRepository(dataSource);
    }
}
  • Configuration 파일에서 MemberRepository를 반환하는 코드만 변경해주면 된다.
  • DataSource
    DataSource는 데이터베이스 커넥션을 획득할 때 사용하는 객체다. 스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어둔다. 그래서 DI를 받을 수 있다.

실행

h2 database를 실행시킨 후, spring application을 실행시켜야 한다.
http://localhost:8080 에 접속하면 h2 데이터베이스에 생성했던 회워 목록을 조회할 수 있다.
회원 가입을 하면, 가입된 회원을 h2 콘솔에서도 확인할 수 있다.

설명


  • 개방-폐쇄 원칙(OCP, Open-Closed Principle)
    확장에는 열려있고, 수정, 변경에는 닫혀있다.
  • 다형성
  • 스프링의 DI (Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있다.
  • 데이터를 DB에 저장하므로 스프링 서버를 다시 실행해도 데이터가 안전하게 저장된다.

스프링 통합 테스트

기존 테스트는 순수 자바 테스트코드.
스프링 컨테이너와 DB까지 연결한 통합 테스트를 진행해보자.

MemberServiceIntegrationTest

@SpringBootTest
@Transactional // 테스트 후 rollback
class MemberServiceIntegrationTest {

    // test 시 간단하게 field injection
    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    void join() {
        //given
        Member member = new Member();
        member.setName("spring");

        //when
        Long saveId = memberService.join(member);

        //then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());

    }

    @Test
    void Dup_Member_join(){
        //given
        Member member1 = new Member();
        member1.setName("dup");

        Member member2 = new Member();
        member2.setName("dup");

        //when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
    }
}
  • @SpringBootTest
    스프링 컨테이너와 테스트를 함께 실행한다.
  • @Transactional
    테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.
  • 순수 자바 단위 테스트가 더 좋은 테스트일 확률이 높다.

실행

  • 테스트를 위해 h2 콘솔에서 데이터를 지운다.
    delete from member
  • 실행 시 통합 테스트이므로 테스트 로그에 spring 로그도 뜬다.

0개의 댓글