스프링DB - JDBC 이해

meluu_·2024년 2월 5일
0

스프링 DB

목록 보기
1/9
post-thumbnail

🌿 시작하기 앞서


스프링 부트 3.2.2 버전을 기준으로 작성됨
H2 데이터베이스 Version 2.2.224 (2023-09-17)

🌱 JDBC


애플리케이션을 개발할 때 중요 데이터는 DB에 보관한다.

클라이언트 -> 애플리케이션 서버 -> DB

사용법

  1. 커넥션 연결 : 주로 TCP/IP 커넥션 연결
  2. SQL 전달 : DB가 이해 가능한 SQL을 연결된 커넥션을 통해 DB에 전달
  3. 결과 응답 : DB는 SQL을 처리하고 결과를 애플리케이션 서버에 반환

문제는 DB마다 커넥션 연결, SQL 전달, 결과응답 방법이 다 다르다.


✔️ JDBC 표준 인터페이스


JDBC(Java Database Connectivity) : 자바에서 DB에 접속할 수 있도록 하는 자바 API
JDBC는 DB에서 자료를 쿼리하거나 업데이트하는 방법 제공

각 DB 회사는 표준 인터페이스를 구현한 라이브러리를 제공 : 드라이버

애플리케이션 로직은 인터페이스를 의존(추상화)
->의존성 역전 원칙 (DIP)

이로써 다른 종류의 DB로 바뀌어도 문제없다.



✔️ JDBC와 최신 데이터 접근 기술


JDBC : 1997년 기술
JDBC를 편리하게 사용하는 기술이 존재

SQL Mapper || ORM

  • 장점 : JDBC를 편리하게 사용하도록 도와준다.
    • SQL 응답 결과를 객체로 편리하게 변환해준다.
    • JDBC의 반복 코드를 제거해준다.
  • 단점 : SQL 직접 작성
  • 대표 기술 : 스프링 JdbcTemplate, Mybatis

  • ORM은 객체를 관계형 데이터베이스 테이블과 매핑해주는 기술로 반복적인 SQL을 직접 작성하지 않고, 개발자 대신에 SQL을 동적으로 만들어서 실행해준다.
    • 각각의 데이터베이스마다 다른 SQL을 사용하는 문제도 중간에서 해결해준다.
  • 대표 기술 : JPA, 하이버네이트, 이클리스링크
  • JPA는 자바 진영의 ORM 표준 인터페이스, 구현체가 하이버네이트와 이클립스 링크

SQL Mapper vs ORM 기술

SQL Mapper
SQL만 직접 작성하면 나머지 번거로운 일은 SQL Mapper가 대신 해결

ORM
SQL 직접 작성하지 않아도 되어 개발 생산성이 매우 높다.
쉬운 기술이 아니므로 실무에서 사용하려면 깊은 학습 필요

SQL Mapper을 쓰든 ORM을 쓰든 결국에는 JDBC가 최종적으로 Low 레벨에서 처리하는 것이기에 JDBC는 필수적으로 익혀야한다.



✔️ 데이터베이스 연결


import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.SQLException;

@Slf4j
public class DBConnectionUtil {

    public static Connection getConnection() {
        try {
            Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            log.info("get connection={}, class={}", connection, connection.getClass());
            return connection;

        } catch (SQLException e) {
            throw new IllegalStateException(e);
        }
    }
}

import java.sql.Connection;
Connection은 sql의 Connection을 사용해야한다.


public static Connection getConnection(String url, String user, String password) throws SQLException

데이터베이스에 연결하려면 JDBC가 제공하는
DriverManager.getConnection()을 사용

파라미터로 넘어온 정보를 통해서 라이브러리에 있는 데이터베이스 드라이버를 찾아 해당 드라이버가 제공하는 커넥션을 반환해준다.

연결 이해


JDBC는 java.sql.Connection 표준 커넥션 인터페이스를 정의
H2 데이터베이스 드라이버는 JDBC Connection 인터페이스를 구현한 org.h2.jdbc.JdbcConnection
구현체를 제공

DriverManager 커넥션 요청 흐름

DrverManager
라이브러리에 등록된 DB 드라이버를 관리
커넥션을 획득하는 기능 제공

  1. 애플리케이션 로직에서 커넥션이 필요하면 DriverManager.getConnection()을 호출
  2. 라이브러리에 등록된 드라이버 목록을 자동으로 인식
    드라이버들에게 순서대로 다음 정보를 넘겨서 커넥션 획득가능 여부 확인
    • URL
    • 이름, 비밀번호등 접속에 필요한 추가 정보
    • 드라이버는 URL 정보를 체크해서 본인이 처리가능하면 실제 DB에 연결해서 커넥션을 획득 및 클라이언트에게 반환
    • 처리 불가일 경우 실패 결과를 반환 및 다음 드라이버에게 순서 넘김
  3. 찾은 커넥션 구현체가 클라이언트에 반환



✔️ JDBC 개발 - 등록


회원 데이터를 DB에 관리하는 기능 개발

H2 DB에 미리 member 테이블을 만들어 두어야함

drop table member if exists cascade;
create table member (
 member_id varchar(10),
 money integer not null default 0,
 primary key (member_id)
);

Member

@Data
public class Member {

    private String memberId;
    private int money;

    public Member() {
    }

    public Member(String memberId, int money) {
        this.memberId = memberId;
        this.money = money;
    }
}
public Member save(Member member) throws SQLException {
        String sql = "insert into member(member_id, money) values (?, ?)";

        Connection con = null;
        PreparedStatement pstmt = null;   

        try {
            // DB 커넥션 획득
            con = getConnection();
            pstmt = con.prepareStatement(sql); // SQL과 파라미터로 전달할 데이터들을 준비 
            // sql에 대한 파라미터 바인딩 (?) 에 값 넣기
            pstmt.setString(1, member.getMemberId()); // 첫 번째 ?에 값 넣기
            pstmt.setInt(2, member.getMoney()); // 두 번째 ?에 값 넣기
            int count = pstmt.executeUpdate(); // 영향받은 DB row 수를 반환
            return member;
        } catch (SQLException e) {
            log.error("db error", e);
            throw e;
        } finally {
            close(con, pstmt, null);
        }

    }
    
private void close(Connection con, Statement stmt, ResultSet rs) {

    if (rs != null) {
        try {
            rs.close();
        } catch (SQLException e) {
            log.info("error", e);
        }
    }

    if (stmt != null) {
        try {
            stmt.close(); 
        } catch (SQLException e) {
            log.info("error", e);
        }
    }

    if (con != null) {
        try {
            con.close();
        } catch (SQLException e) {
            log.info("error", e);
        }
    }

}

private Connection getConnection() {
    return DBConnectionUtil.getConnection();
}

❗ 리소스 정리

쿼리 실행을 끝내고 리소스를 정리해 주어야한다.
사용의 역순으로 정리해주면 된다.
finally 구문에 작성해야한다.

  1. Connection start
  2. PreparedStatement start
  3. PreparedStatement close
  4. Connection close
  • PreparedStatement 는 Statement 의 자식 타입인데, ? 를 통한 파라미터 바인딩을 가능하게 해준다.
  • SQL Injection 공격을 예방하려면 PreparedStatement 를 통한 파라미터 바인딩 방식을 사용

✔️ JDBC 개발 - 조회


public Member findById(String memberId) throws SQLException {
    String sql = "select  * from member where member_id = ?";

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

    try {
        con = getConnection();
        pstmt = con.prepareStatement(sql);
        pstmt.setString(1, memberId);

        rs = pstmt.executeQuery();
        if (rs.next()) {
            Member member = new Member();
            				//  String으로 변환
            member.setMemberId(rs.getString("member_id"));
            				// int로 변환
            member.setMoney(rs.getInt("money"));
            return member;

        } else {
            throw new NoSuchElementException("member not found memberId = " + memberId);
        }

    } catch (SQLException e) {
        log.error("error", e);
        throw e;

    } finally {
        close(con, pstmt, rs);
    }
}

데이터 변경 : pstmt.executeUpdate()
데이터 조회 : pstmt.executeQuery() , 결과를 ResultSet 에 담아서 반환


ResultSet

select 쿼리의 결과가 순서대로 들어간 데이터 구조
ex) select memeber_id, money -> member_id, money 라는 이름의데이터가 저장

내부에 커서(cursor)를 이동해서 다음 데이터를 조회

rs.next : 커서가 다음으로 이동, 최초 커서는 데이터를 가르키고 있지 않기에 최소 한 번은 호출해야 데이터 조회 가능

  • 데이터 존재시 true 없으면 false
  • false면 데이터가 더이상 존재하지 않는다는 뜻


✔️ JDBC 개발 - 수정, 삭제


public void update(String memberId, int money) throws SQLException {
    String sql = "update member set money=? where member_id=?";

    Connection con = null;
    PreparedStatement pstmt = null;

    try {
        con = getConnection();
        pstmt = con.prepareStatement(sql);
        pstmt.setInt(1, money);
        pstmt.setString(2, memberId);
        int resultSize = pstmt.executeUpdate();
        log.info("resultSize={}", resultSize);

    } catch (SQLException e) {
        log.error("db error", e);
        throw e;
    } finally {
        close(con, pstmt, null);

    }

}


public void delete(String memberId) throws SQLException {
    String sql = "delete from member where member_Id = ?";

    Connection con = null;
    PreparedStatement pstmt = null;

    try {
        con = getConnection();
        pstmt = con.prepareStatement(sql);
        pstmt.setString(1, memberId);
        pstmt.executeUpdate();

    } catch (SQLException e) {
        log.error("db error ", e);
        throw e;
    } finally {
        close(con, pstmt, null);

    }
}


📓 정리


  1. sql문 짜고
  2. Connection 얻고
  3. PreparedStatement을 얻고
  4. sql에 대한 파라미터 바인딩을 해주고
  5. DB에 반영한다.
  6. 사용이 끝나면 리소스를 정리한다.
  • 예외는 SQLException 으로 잡는다.


🔖 학습내용 출처

스프링 DB 1편 - 데이터 접근 핵심 원리

profile
열심히 살자

0개의 댓글

관련 채용 정보