티스토리 뷰

728x90
반응형

스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 김영한 인프런 강의 참고

이전 포스팅 : 2023.02.17 - [Web Programming/Spring&Spring Boot] - [Spring Boot] 입문 - 순수 JDBC로 DB 연결하기(feat. mysql) - 1

  • JDBC API
  • mysql 설치
  • 순수 JDBC API로 DB 접근하기
  • DB 교체를 위한 스프링 설정 변경

순수 JDBC API로 DB 접근하기


MemberRepository를 JDBC API로 다시 구현한다.

.../repository/JdbcMemberRepository.java

// ...

public class JdbcMemberRepository implements MemberRepository {

    // database 연결 정보를 담고 있는 객체
    private final DataSource dataSource;

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

    @Override
    public Member save(Member member) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Optional<Member> findById(Long id) {
        // TODO Auto-generated method stub
        return Optional.empty();
    }

    @Override
    public Optional<Member> findByName(String name) {
        // TODO Auto-generated method stub
        return Optional.empty();
    }

    @Override
    public List<Member> findAll() {
        // TODO Auto-generated method stub
        return null;
    }

}

우선 기본적인 구조는 위와 같다.

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

각 메서드의 구현은 코드가 상당히 길어지고 반복이 많기 때문에 대표적으로 save 메서드의 코드만 분석해 보겠다. (전체 코드는 github참고 : https://github.com/on1ystar/springForPractice/blob/master/practice/src/main/java/com/example/practice/repository/JdbcMemberRepository.java)

// ...

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

    // DB의 연결 정보를 가지고 일정 시간 동안 DB와 연결할 수 있도록 통로 역할을 하는 객체
    Connection conn = null;
    // 다양한 SQL 구문들을 정의하고 바인딩 하는 방법들과 실제 DB로 전송하는 방법들이 정의된 객체
    PreparedStatement pstmt = null;
    // sql 쿼리 결과를 저장하는 데이터 집합 객체
    ResultSet rs = null;

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

        // sql string의 첫 번째 '?'에 member의 name 값을 바인딩
        pstmt.setString(1, member.getName());

        // DB에 바인딩 된 sql 전송
        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);
    }
}

// ...

// 할당한 리소스 해제
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();
    }
}

// Connection 객체 리소스 해제
private void close(Connection conn) throws SQLException {
    DataSourceUtils.releaseConnection(conn, dataSource);
}

JDBC api는 예외를 많이 던지므로 전체적으로 try ~ catch 문을 사용한다.

java.sql

Java 프로그래밍 언어를 사용하여 데이터 소스(일반적으로 관계형 데이터베이스)에 저장된 데이터에 액세스하고 처리하기 위한 API를 제공

주요 클래스 및 인터페이스

  • Connection : Statement를 만들고, 연결 및 속성을 관리하는 방법을 제공
  • Statement : 기본 SQL 문을 보내는 데 사용됨
  • PreparedStatement : 미리 생성된 Statement 또는 기본 SQL 문을 전송하는 데 사용됨 (derived from Statement)
  • ResultSet : 데이터베이스 결과 집합을 나타내는 데이터 테이블

conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)

미리 컴파일 된 SQL 문을 나타내는 객체를 반환

  • sql : 쿼리를 날릴 sql문을 String 타입으로 생성해 넘겨줌. 이때 ?를 사용하면 추후 동적으로 값을 넣어 바인딩할 수 있음
  • Statement.RETURN_GENERATED_KEYS : 자동으로 생성되는 sequence 키를 추후에 반환받을 수 있음

pstmt.executeUpdate()

PreparedStatement 객체에서 SQL 문을 실행.

INSERT, UPDATE 또는 DELETE와 같은 SQL Data Manipulation Language(DML) 문이거나 DDL 문과 같이 아무것도 반환하지 않는 SQL 문

참고로 SELECTexecuteQuery()이고 반환 타입은 ResultSet

next()

ResultSet 객체는 반환된 결과를 테이블 형식으로 저장하고 있으며, 현재 행을 가리키는 cursor값을 가지고 있음

커서를 현재 위치에서 한 행 앞으로 이동시키며, 다음 행이 있으면 true, 현재 행이 마지막 행이라 다음 행이 없으면 false를 반환

리소스 해제

할당한 순서의 반대로 해제

  1. rs.close()
  2. pstmt.close()
  3. DataSourceUtils.releaseConnection(conn, dataSource)

DB 교체를 위한 스프링 설정 변경


우리는 이전에 DB가 교체될 것을 가정하고 MemberRepository를 인터페이스로 설계했다. 이를 구현한 구현체가 MemoryMemberRepository였으며, 이제 JdbcMemberRepository가 추가되었다.

이제 아래와 같이 스프링 설정 파일 SpringConfig.java에서 memberRepositoryJdbcMemberRepository를 반환하도록 코드를 바꿔주면 간단하게 DB를 메모리에서 mysql로 변경할 수 있다.

.../SpringConfig.java

@Configuration
public class SpringConfig {

    private final DataSource dataSource;

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

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

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

위와 같이 DataSource 객체를 선언해 준 뒤, JdbcMemberRepository 객체를 생성하는 생성자 매개변수 값으로 넣어준다.

그러면 스프링이 memberRepository 빈 생성 시, DataSource 객체를 알아서 DI로 주입한다.

위와 같은 설계 방식을 개방-폐쇄 원칙(OCP, Open-Closed Principle)이라고 한다. 이는 확장에는 열려있고, 수정, 변경에는 닫혀있다는 특징이 있다.

(참고 :2023.02.07 - [프로그래밍 언어 공부/Java] - [Java] 객체 지향 설계 5원칙 - SOLID)

더불어 스프링의 DI (Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있다.

 

Reference

728x90
반응형
댓글