[Spring] Spring과 DB 연결하기 with JDBC

김유진·2022년 11월 21일
0

Spring

목록 보기
11/12
post-custom-banner

1. 데이터베이스에 접근할 준비하기

이제 Spring으로 제작한 App과 DB를 연결하여 실제 sql 쿼리를 통하여서 database를 관리할 수 있는 환경을 만들어야 한다.

먼저 build.gradle에 아래와 같이 세팅하여

implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'

h2 데이터베이스를 사용할 것임을 안내하고, jdbc 패키지를 사용할 것임에 대해서도 안내하게 된다.

그리고 src 폴더의 application.properties에도 아래와 같이 세팅한다.

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver

이렇게 하여 내가 설저한 서버에 대한 url을 연결해 주고, DB 드라이버의 이름을 h2라고 넘겨준다. 원래대로라면 이곳에 아이디와 비밀번호를 작성해야 하는데 h2는 그 과정을 생략해도 된다.

단 스프링부트 2.4 이상부터는 spring.datasource.username=sa 를 꼭 추가해주어야 한다.

이렇게 하면 데이터베이스에 대해서 접근하기 위한 준비가 모두 마치게 된다.

2. 직접 JDBC 리포지토리 구현하기

JDBC API로 하나하나 리포지토리에 있는 내용을 불러와서 DB와 연결, 구현해야 한다. 그런데 이렇게 직접 코딩하는 것은 20년 전 이야기이므로 옛날 옛날 개발하셨던 분들이 많이 고생하셨구나.. 하고 넘어가자. 그래도 옛것을 이해해야 현재 것을 잘 할 수 있는 법!!!

DB에 붙이기 위해 DataSource가 필요하니 연결시켜주자.

public class JdbcMemberRepository implements MemberRepository{

    private final DataSource dataSource;

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

일단 MemberRepository를 implement하여서 연결해주고 DB를 붙이기 위해서 DataSource 관련한 코드를 작성한 내용이다. spring이 미리 알아서 만들어준 dataSource를 주입받게 될 것이다.

먼저 save 기능부터 코딩해보자!

public Member save(Member member) {
        String sql = "insert into member(name) values(?)";
        Connection conn = dataSource.getConnection();
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setString(1, member.getName());
        pstmt.executeupdate();
        return null;
    }

이렇게 되는데 쿼리문 날릴 sql 미리 지정해두고, 가져온 member에 대해서 getName()을 통하여 이름을 뽑아내서 실제로 쿼리저장을 할 수 있는 것이다.
이전에 우리가 구현한 부분에서 id값이 자동으로 1씩 더해질 수 있도록 하였는데 그 부분에 대해서도 수정해보자. 디비에서 먼저 시퀀스값이 얼마인지 가져오는 일을 해야 하므로!

이렇게 하나하나 하다가 한세월을 보내 전체코드는 아래와 같다.

package Inflearn_spring.studyspring.repository;

import Inflearn_spring.studyspring.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();
            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);
    }

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

그리고 configuration부분의 코드를 변경해주어야 한다.

@Configuration
public class SpringConfig {
    
    private final DataSource dataSource;
    
    public SpringConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

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

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

이게 바로 객체지향의 장점 아닐까.. Service에 있는 코드를 하나도 수정하지 않아도 되고, 레포지토리가 변경된 것과 소스코드가 추가된 것에 대해서만 코드를 부품 바꾸어 끼듯이 바꿔줄 수 있으니까 얼마나 편리한가..!!
이렇게 코드를 마무리지을 수 있다!

그럼 실제로 sql을 날렸을 때 코드가 잘 저장되는지 확인해보도록 하자.

타란~ DB에 저장된 내용이 화면에 잘 뿌려지고 있따.

DataSource가 뭐지?

DataSource는 데이터 베이스 커넥션을 획득할 때 사용하는 객체다. 스프링부트는 데이터베이스 커넥션
정보를 바탕으로 DataSource를 생성하고 스프링빈으로 만들어둔다. 그래서 DI를 받을수 있다.

우리가 기존에 만들어 둔 MemberService에서 파생된 interface를 기반으로 JDBC를 사용하는 레포지토리를 만들어 본 것이다.

근데 지금은 스프링 컨테이너 관점에서 보게 된다면 아래와 같은 형식을 띠고 있겠지!!

업로드중..
이렇게~

스프링의 멋있는 D.I

스프링의 DI(Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로도 구현된 클래스를 변경할 수 있다.
이제 데이터를 DB에 저장하기 때문에, 스프링 서버를 다시 실행해도 데이터가 안전하게 저장될 수 있는 것이다.

post-custom-banner

0개의 댓글