들어가며

  • 이전 포스트에서 JDBC의 개념, 기술들의 설명에 대해 알아보았다.
  • 이번 포스트에서는 코드를 통해 어떻게 사용하는지에 대해 알아본다.

 

 

 

JDBC를 사용한 회원 정보 등록

  • 회원 정보를 등록하는 예제를 만들어보자. 여기서는 H2 데이터베이스를 사용하고, 이미 연결이 완료되었다고 가정한다.
  • 가장 먼저 회원에 대한 정보를 구현해야 한다. 구현할 회원은 매우 간단한 상태만을 가지고 있다.
    • 먼저 회원의 id이다. 이것으로 회원을 유일하게 식별할 수 있다.
    • 그 다음이 회원이 가지고 있는 돈 액수이다.
  • 이를 클래스로 나타내면 다음과 같다.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Member{
    private String memberId;
    private int money;
}
  • 롬복을 사용하여 코드를 간소화하였다. 이 객체를 이후에 member 테이블에 존재하는 데이터를 저장하거나 조회할 때 사용할 것이다.

 

 

DB 커넥션 획득

  • 데이터베이스에 접속하기 위해서는 접속 URL, 사용자 이름과 비밀번호가 필요하다.
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 = "";
}
  • 이제 DB에 접속할 커넥션을 획득하는 클래스를 만들어 보자
import static hello.jdbc.connection.ConnectionConst.*;

public class DBConnectionUtil{
    public static Connection getConnection(){
        try{
            Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            return connection;
        } catch (SQLException e){
            throw new IllegalStateException(e);
        }
    }
}
  • 커넥션을 획득하는 것은 DriverManager의 getConnection을 통해 얻을 수 있다.
  • DriverManager는 라이브러리에 등록된 드라이버 목록 중 커넥션을 수행하고 획득할 수 있는 드라이버를 선택하여 커넥션을 획득한다.
    • 이 과정은 위의 URL을 통해 수행되는데 DriverManager는 라이브러리에 등록된 각 DB의 드라이버 객체들을 순회하면서 해당 URL을 처리할 수 있는지를 확인한다.
    • 해당 URL을 처리할 수 있는 드라이버를 찾는다면 해당 드라이버에 커넥션을 가져오도록 요청하고, 커넥션 구현체가 클라이언트에 반환된다.

 

 

 

Repository 구현

  • Repository는 DB와 직접 통신하여 데이터를 주고 받는 계층을 의미한다. DAO라고도 한다.
  • 먼저 회원의 데이터를 저장하는 기능을 Repository에 만들어보자.
public class MemberRepositoryV0{

    public Member save(Member member) throws SQLException{
        String sql = "insert into member(member_id, money) values(?, ?)";
        
        Connection conn = null;
        PreparedStatement pstmt = null;
        
        try{
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            
            pstmt.setString(1, member.getMemberId());
            pstmt.setInt(2, member.getMoney());
            
            pstmt.excuteUpdate();
            
            return member;
        } catch (SQLException e){
            throw e;
        } finally{
            close(conn, pstmt, null);
        }
    }
    
    public void close(Connection conn, Statement stmt, ResultSet rs){
        if(rs != null){
            try{
                rs.close();
            } catch(SQLException e){
               throw e;
            }
        }
        
        if(stmt != null){
            try{
                stmt.close();
            } catch(SQLException e){
               throw e;
            }
        }
        
        if(conn != null){
            try{
                conn.close();
            } catch(SQLException e){
               throw e;
            }
        }
    }
    
    private Connection getConnection(){
        return DBConnectionUtil.getConnection();
    }

}
  • 새로운 것들이 많이 등장했다 빠르게 하나씩 훑어보자.
  • conn.prepareStatement(sql)을 통해 데이터베이스에 전달할 SQL과 파라미터로 전달할 데이터들을 준비한다.
    • 파라미터로 전달할 데이터들은 PreparedStatement 타입의 pstmt.setString, setInt 등으로 전달한다. 타입에 따라 setXXX로 지정이 되어 있다.
    • setXXX의 경우 첫 번째 파라미터는 대응되는 인덱스 번호(위치), 두 번째 파라미터에는 대응되는 값이 들어간다.
  • 이후 pstmt.executeUpdate()를 통해 SQL을 실제 DB로 전달한다. executeUpdate() 메서드는 해당 SQL 연산으로 인해 영향받은 행의 수를 반환한다.
  • 이후에 모든 동작이 종료되었기 때문에 DB를 연결하는데 든 리소스들을 모두 반환해야 한다. close() 함수를 보면 어떻게 연결을 종료하는지에 대해 구현되어 있다.
    • 주의할 점은 저 순서대로 수행해야 한다는 것이다. ResultSet이라는 연산의 결과를 받는 객체부터 먼저 close 되어야 한다. 이후에 statement가 close되어야 하고 최종적으로 Connection이 close되어야 한다.
    • 이는 Connection이 먼저 성립되고 나서야 Statement가 성립되고, Statement가 성립되어야 ResultSet도 성립되기 때문에 제거할 때는 역순으로 제거하는 것이다.
    • 만약 이러한 제거 방식을 하지 않았을 경우 리소스가 계속 남아있어 장애가 발생할 수 있다.
  • 해당 동작이 잘 수행되는지 테스트 코드를 만들어 보자.

 

 

 

동작 테스트 코드

class MemberRepositoryV0Test{
    MemberRepositoryV0 memberRepository = new MemberRepositoryV0();
    
    @Test
    void crud() throws SQLException{
        Member member = new Member("memberA", 10000);
        repository.save(member);
    }
}
  • @Test 어노테이션은 테스트 프레임워크에서 사용하는 어노테이션으로 해당 메서드를 테스트로 동작시키겠다는 것이다. 지금 crud 메서드에서는 새로운 회원을 생성하여 DB에 저장하는 테스트를 수행하고 있다.
  • 해당 코드를 수행하고 H2 DB 콘솔에서 SELECT * FROM MEMBER을 수행해보면 새로운 멤버 튜플이 생성된 것을 확인할 수 있다.
    • 물론 한 번 더 수행하면 PK 중복 오류가 발생한다. 이는 PRIMARY KEY가 memberId로 지정되어 있기 때문에 발생한다. 따라서 여러 번 수행하려면 DELETE FROM MEMBER을 사용하여 모두 지우고 다시 수행해야 한다.
복사했습니다!