복습을 위한

DriverManager, Connection, Statement, ResultSet 본문

JDBC

DriverManager, Connection, Statement, ResultSet

ho042479 2024. 1. 30. 15:32

SQL Mapper인 JdbcTemplate과 MyBatis나 ORM기술인 JPA나 결국 내부에서는JDBC를 사용한다. 

JDBC가 어떻게 동작하는지 알아보자. 

대표적으로 다음 3가지 기능을 표준 인터페이스로 정의해서 제공한다.

java.sql.Connection - 연결

java.sql.Statement - SQL을 담은 내용  (구현할 땐 상속받은 PreparedStatement 권장(바인딩가능, 기능 더많음, 보안강화)

java.sql.ResultSet - SQL 요청 응답

 

자바는 이렇게 표준 인터페이스를 정의해두었다. 이제부터 개발자는 이 표준 인터페이스만 사용해서 개발하면 된다. 그런데 인터페이스만 있다고해서 기능이 동작하지는 않는다. 이 JDBC 인터페이스를 각각의 DB 벤더(회사)에서 자신 의 DB에 맞도록 구현해서 라이브러리로 제공하는데, 이것을 JDBC 드라이버라 한다. 예를 들어서 MySQL DB에 접근 할 수 있는 것은 MySQL JDBC 드라이버라 하고, Oracle DB에 접근할 수 있는 것은 Oracle JDBC 드라이버라 한다.

 

 

 

 

 

 

나는  h2데이터베이스를 사용한 간단한 학습을 해보겠다.

 

 


 h2데이터베이스에 접근하기 위한 정보를 상수로 일단 만들어두었다. 

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 = "0000";
}

 

 

 

 

데이터베이스에 연결하려면 JDBC가 제공하는 DriverManager.getConnection(..) 를 사용하면 된다. 이렇게 하면 라이브러리에 있는 데이터베이스 드라이버를 찾아서 해당 드라이버가 제공하는 커넥션을 반환해준다. 여기서는 H2 데이터베이스 드라이버가 작동해서 실제 데이터베이스와 커넥션을 맺고 그 결과를 반환해준다.(프로젝트를 시작할 때 사용할 데이터베이스 드라이버를 라이브러리에 추가해주자) 

@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();
        }
    }
}
//데이터베이스에 연결하려면 JDBC가 제공하는 DriverManager.getConnection(..) 를 사용하면 된다. 이렇게
//하면 라이브러리에 있는 데이터베이스 드라이버를 찾아서 해당 드라이버가 제공하는 커넥션을 반환해준다. 여기서는
//H2 데이터베이스 드라이버가 작동해서 실제 데이터베이스와 커넥션을 맺고 그 결과를 반환해준다

 

 

 

insert,select,update,delete코드

/*
* JDBC -DriverManger사용  가장 예전 방식
* */

@Slf4j
public class MemberRepositoryV0 {

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

        Connection con = null;//연결객체
        PreparedStatement pstmt = null; //이것을 사용해 데이터베이스 쿼리날림

        try {
            con = getConnection();//연결을 얻어온다.
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, member.getMemberId());//?,?에 값 바인딩하기
            pstmt.setInt(2, member.getMoney());
            pstmt.executeUpdate();//실제전달메소드  데이터변경할때 //해당메소드는 쿼리를 실행하고 영향받은 row수를 반환한다.(int반환)
            return member;
        } catch (SQLException e) {
            log.error("db error", e);
            throw e;
        } finally {
            close(con, pstmt, null);   //다 끝나면 닫아줘야한다. 닫을 때는 역순
        }
    }
    
    //주어진 memberId에 해당하는 회원 정보를 데이터베이스에서 조회하고, 그 결과를 Member 객체에 저장하는 메소드
    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();//select 쿼리 결과를 담고있는 ResultSet객체반환
            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){
            log.error("db error", e);
            throw e;
        }finally {
            close(con,pstmt,rs);
        }
    }

    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();//데이터변경 //동시에 영향받은 줄 수를 반환받을 수 있다. 100줄변경시 100반환
            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();//데이터변경 //동시에 영향받은 줄 수를 반환받을 수 있다. 100줄변경시 100반환
        } catch (SQLException e) {
            log.error("db error", e);
            throw e;
        } finally {
            close(con, pstmt, null);   //다 끝나면 닫아줘야한다.
        }
    }

    //Statement는 sql을 그대로 넣는거고  prepareStatement는 파라미터 바인딩이 가능. 기능더많음
    //prepareStatement는 Statement를 상속받음
    //닫는 메소드, 생성과 반대로 역순으로
    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();
    }
}

 

요약하자면 데이터베이스와 연결을 위해 connection객체를 가져와야한다. 연결에 필요한 연결객체라 보면 편할 것 같다.

그리고 그녀석이 prepareStatement객체를 생성한다. 그 놈은 컴파일 된 sql문을 사용하고, 동적으로 값을 설정할 수 있다. 

prepareStatement객체는 위 코드에서 쿼리에 값을 바인딩하고 그걸로 데이터를 조회하거나 변경한다.

조회같은 경우 결과값을 resultset객체로 반환하고 우리는 그것을 활용해 원하는 member객체를 조회할 수 있다. 변경의 경우는 영향받은 줄 수를 int로 반환한다.

 

그리고 연결을 닫아줘야한다. 여기서 중요한 것을 역순으로 해야한다. 그러므로 가장 먼저 호출된 Connection객체를 가장 나중에 닫아준다. 

 

 

 

 

나는 h2데이터베이스와 연결하기 위해 Connection객체가 필요해 DriverManager를 통해 h2드라이버로부터 h2커넥션객체를 얻어올 수 있었고 그것으로 데이터베이스와 연결을 해 원하는 작업을 할 수 있었던 것이다.  

 

DriverManager 는 라이브러리에 등록된 드라이버 목록을 자동으로 인식한다. 이 드라이버들에게 순서대로 다음 정보를 넘겨서 커넥션을 획득할 수 있는지 확인한다.

 

여기서 각각의 드라이버는 URL 정보를 체크해서 본인이 처리할 수 있는 요청인지 확인한다. 예를 들어서 URL이 jdbc:h2 로 시작하면 이것은 h2 데이터베이스에 접근하기 위한 규칙이다. 따라서 H2 드라이버는 본인이 처리할 수 있으므로 실제 데이터베이스에 연결해서 커넥션을 획득하고 이 커넥션을 클라이언트에 반환한다

 

 

 

 

 

 

 

참조

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1

'JDBC' 카테고리의 다른 글

DataSource사용하기  (1) 2024.01.30
커넥션 풀  (0) 2024.01.30