본문 바로가기

Back-End/DataBase

JDBC - CRUD

내가 JDBC를 공부하는 이유 -> 일단 이러한 LOW레벨의 동작원리를 알아야지 나중에 Template이나 JPA를 사용할 때 어쨋든 이 JDBC 기반으로 동작하기 때문에 이 근본이 되는 기술을 알아야한다고 생각했다.


일단 들어가기 앞서 
전체적인 JDBC를 이용한 DB 연결 및 데이터 처리 흐름

  1. 애플리케이션 
    • "DB와 연결해서 데이터를 가져오거나 저장하고 싶다"고 요청
  2. DriverManager
    • 등록된 여러 데이터베이스 드라이버 중에서 요청된 DB 유형(ex. MySql)에 맞는 드라이버를 선택
    • DB 드라이버는 URL로 판단! (ex. Jdbc:mysql, Jdbc:h2 ..)
  3. DB 드라이버
    • 선택된 드라이버가 DB와 TCP/IP로 연결을 시도
    • DB에 ID, PW를 보내 인증 과정을 진행.
    • 연결이 성공하면 Connection 객체를 만들어 애플리케이션에 반환
  4. Connection
    • 데이터베이스와 연결된 상태를 유지하며, SQL을 전달하거나 결과를 받는 역할.
  5. PreparedStatement
    • Connection을 통해 SQL문을 실행
    • SQL에 전달할 값들을 안전하게 바인딩하고, 최적화된 방식으로 DB에 전달
  6. DB
    • 전달받은 SQL을 실행하고, 그 결과를 반환
    • 애플리케이션은 결과를 받아서 필요에 맞게 처리.

1. 먼저 SQL Table 생성

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

2. DB와 연결

먼저 DB에 접속하는데 필요한 기본 정보를 작성

public abstract class ConnectionConst {
	public static final String URL = "jdbc:h2:tcp://localhost/~/test";
    public static final String USERNAME = "sa";
    public static final String PASSWORD = "";
}

이제 JDBC를 사용해서 실제 DB와 연결하는 코드 작성

public class DBConnectionUtil {

    public static Connection getConnection() {
        try {
        //DriveManager가 URL의 jdbc:h2로 시작하는 것을 보고 라이브러리에 등록된h2 DB에 접근을 시도한것.
	//만약 h2가 아니라 Mysql이면 MySql에 접근을 시도
            Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            return connection;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

근데 DriverManager가 뭔데?
-> DriverManagerDB 드라이버(MySql, H2..)를 통해 커넥션을 요청하고, DB 드라이버가 커넥션을 생성한 뒤 반환해주는 역할
쉽게 얘기해서 말 그대로 운전기사 관리자 같은 역할이다. 운전기사(DB 드라이버)에게 목적지(DB정보)를 알려주고, 운전기사(드라이버)실제 차(커넥션)를 만들어서 가져오는것.

  1. 애플리케이션이 DriverManager에게 커넥션을 요청
    - DriverManager.getConnection(URL, USERNAME, PASSWORD);
  2. DriverManager는 내부에 등록된 DB 드라이버들에게 "이 URL로 연결 좀 해줘!" 라고 요청
  3. DB 드라이버(실제 커넥션 생성)는 
    - DB와 연결(TCP/IP)을 맺고, ID/PW를 통해 인증한 후, DB 세션을 생성하고, 커넥션 객체를 반환한다.

그럼 또 궁금한게 있을 것이다. Connection이 뭐하는 애인데?
->

  1.  DB와 연결 관리
    - Connection 객체는 TCP/IP로 연결된 DB와의 통신을 관리한다.
    - 예를 들어, 연결을 열고(open), 필요한 작업을 처리하고, 끝나면 닫아(close)해준다.
  2. SQL 실행
    - SQL 문장을 DB로 전달해 실행할 수 있다.
    - Statement나 PrearedStatement를 사용해 SQL을 실행하고 결과를 받는데 활용된다.
  3. 트랜잭션 관리
    - 여러 SQL 작업을 하나의 묶음으로 처리하고, 작업 도중 문제가 발생하면 rollback을 할 수 있다.
    - 예를 들어 connection.commit(), connection.rollback()

3. 등록

3.1  회원 클래스 생성

@Data
public class Member {

    private String memberId;
    private int money;

    public Member() {}

    public Member(String memberId, int money) {
        this.memberId = memberId;
        this.money = money;
    }
}

3.2 멤버 생성

public class MemberRepositoryV0 {
    public Member save(Member member) {
    	// 데이터베이스에 전달할 SQL을 정의.
        String sql = "insert into member(member_id, money) values(?, ?)";

        Connection con = null;
        PreparedStatement pstmt = null;

        try {
            con = DBConnectionUtil.getConnection(); //데이터베이서 커넥션을 획득.
            pstmt = con.prepareStatement(sql); //DB에 전달할 sql과 파라미터로 전달할 데이터들을 준비
            pstmt = setString(1, member.getMemberId()); //sql의 첫번째 ?에 값을 지정
            pstmt = setInt(2, member.getMoney());
            pstmt = executeUpdate();  
            return member;
        } catch (SQLException e) {
            throw new RuntimeException(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);
            }
        }
        
    }
}
  • Connection con = null 
    • 데이터베이스랑 전화선을 만든다고 생각하면 된다.
      • 데이터베이스에 말하려면 먼저 연결을 해야하니깐 con은 전화선 역할이다. 아직 연결 전이라 null로 시작하고, 나중에 연결하면 값이 들어가는 구조.
  • con = DBConnectionUtil.getConnetcion()
    • 이제 진짜로 데이터베이스랑 연결
      • DBConnectionUtil.getConnection()은 전화선을 진짜로 연결해주는 작업.
      • 연결 성공하면 con에 연결에 성공했다고 정보를 넣어준다.
      • 이러면 DB와 대화할 준비가 완료!
  • PreparedStatement pstmt = null;
    • 데이터베이스에 보낼 문자를 쓰는 도구
      • 데이터베이스한테 "뭐 좀 해줘"라고 말하려면 SQL이란 언어를 써야한다.
        pstmt는 이 메시지를 준비하고 보낼 준비를 하는 역할을 한다.
  • pstmt = con.prepareStatement(sql);
    • 데이터베이스한테 보낼 메시지(sql 쿼리)를 준비하는 과정
      • 예를 들어"insert into member(member_id, money) values(?, ?)" 이걸 보낼거라고 알려준다.
        ?는 나중에 이 자리에 값을 넣을거라는 뜻.
      • 그러면 데이터 베이스는 준비하고 기다리고 있다.
  • pstmt.executeUpdate() : Statement를 통해 준비된 SQL을 커넥션을 통해 실제 DB에 전달한다.
    • executeUpdate()는 int를 반환하는데 영향 받은 DB row 수를 반환한다. 여기서는 하나의 row를 등록했으므로 1을 반환한다.
  • PreparedStatement는 statement의 자식타입으로 더 많은 기능이 있다. 예를 들어 ?를 통한 매개변수 바인딩 기능을 제공한다. 
  • 사용을 하고 나면 리소스 정리는 꼭 해주어야한다!!

4. 멤버 조회

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 = DBConnectionUtil.getConnection();
        pstmt = con.prepareStatement(sql);
        pstmt.setString(1, memberId);

        rs = pstmt.executeQuery();

        if (rs.next()) {
            Member member = new Member();
            member.setMemberId(rs.getString("member_id"));
            member.setMoney(rs.getInt("money"));
            return member;
        } else {
            throw new NoSuchElementException("member not found memberId = " + memberId);
        }
    } catch (SQLException e) {
        throw e;
    } finally {
        close(con, pstmt, rs);
    }
}

- findById : 쿼리 실행

  • sql : 데이터 조회를 위한 select SQL를 준비
  • rs = pstmt.executeQuery() 데이터를 변경할 때는 executeUpdate()를 사용하지만, 데이터를 조회할 때는 excuteQuery()를 사용한다. executeQuery()는 결과를 ResultSet에 담아서 반환한다.

- ResultSet : 쿼리 결과 저장, 주로 SELECT 쿼리의 결과를 처리하는데 사용

  •  데이터베이스에서 조회한 결과를 테이블 형식으로 저장 
    • 예를 들어, select member_id, money라고 지정하면 member_id, money라는 이름으로 데이터가 저장
  • 커서(Cursor)를 사용해 결과를 순차적으로 탐색.
  • rs.next()를 통해 다음 데이터를 조회. 참고로 최초의 커서는 데이터를 가리키고 있지 않고 컬럼을 가리키고 있어서 rs.next()를 최초로 한번은 호출해야함.
  • rs.getString("member_id") : 현재 커서가 가리키고 있는 위치의 member_id 데이터를 String 타입으로 반환
  • rs.getInt("money") : 현재 커서가 가리키고 있는 위치의 money 데이터를 int 타입으로 반환
  • 얘도 connection이나 statement처럼 리소스 누수를 방지하기 위해 꼭 자원 정리를 해야한다.

5. 수정 및 삭제

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 = DBConnectionUtil.getConnection();
        pstmt = con.prepareStatement(sql);
        pstmt = setInt(1, money);
        pstmt = setString(2, memberId);
        int resultSize = pstmt.executeUpdate();
    } catch (SQLException e) {
        throw e;
    } finally {
        close(con, pstmt, null);
    }
}
public void delete(String memberId) throws SQLException {
    String sql = "delete member where member_id = ?";

    Connection con = null;
    PreparedStatement pstmt = null;

    try {
        con = DBConnectionUtil.getConnection();
        pstmt = con.prepareStatement(sql);
        pstmt = setString(1, memberId);
        pstmt.executeUpdate();
    } catch (SQLException e) {
        throw e;
    } finally {
        close(con, pstmt, null);
    }
}

JDBC를 해보니 생각보다 단순한데 코드가 많이 지저분하고, 중복되는 코드들이 많다. 이제 기술이 어떻게 변화하는지 궁금해진다!

'Back-End > DataBase' 카테고리의 다른 글

Spring JdbcTemplate의 RowMapper()  (1) 2024.12.09
커넥션 풀과 데이터소스  (0) 2024.12.06
SQL 기초 3 (Join)  (1) 2024.11.08
SQL 기초 2  (2) 2024.11.07
SQL 기초 1  (3) 2024.11.06