Spring JDBC(2) JDBC를 사용해보자

오잉·2023년 4월 13일
0

SPRING

목록 보기
5/15

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

데이터베이스에 접속하는데 필요한 기본 정보(URL, Username, Password)를 편리하게 사용할 수 있도록 상수로 만들었다.

public class DBConnectionUtil {
     public static Connection getConnection() {
          try {
              Connection connection = DriverManager.getConnection(URL,USERNAME,PASSWORD);
              return connection;
          } catch (SQLException e) {
              throw new IllegalStateException(e);
          }
	}
}

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


2. executeUpdate()

insert

 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(); // connection 획득
              pstmt = con.prepareStatement(sql); // statement 세팅
              pstmt.setString(1, member.getMemberId());
              pstmt.setInt(2, member.getMoney());
              pstmt.executeUpdate(); // sql을 connection을 통해 실제 db로 전달
              return member;
          } catch (SQLException e) {
            throw e;
          } finally {
            close(con, pstmt, null);
          }
}
  • executeUpdate()는 INSERT, UPDATE, DELETE에 사용한다.
  • executeUpdate() 은 영향받은 DB row 수를 int로 반환한다.
  • Statement 의 자식 타입인 PreparedStatement는 ?를 통한 파라미터 바인딩을 가능하게 해준다.

update


  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);
          pstmt.executeUpdate(); // 영향받은 DB row 수를 int로 반환한다.
      } catch (SQLException e) {
          throw e;
      } finally {
          close(con, pstmt, null);
	  }
}

delete

   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(); // 영향받은 DB row 수를 int로 반환한다.
      } catch (SQLException e) {
          throw e;
      } finally {
          close(con, pstmt, null);
} }

3. executeQuery()

select

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();  // ResultSet 반환
          
          if (rs.next()) { // findById() 에서는 회원 하나를 조회하는 것이 목적이다. 따라서 조회 결과가 항상 1건이므로 while 대신에 if 를 사용한다.
              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);
} }
  • executeQuery()는 SELECT에 사용한다.
  • executeQuery()의 반환값은 ResultSet이다.

4. ResultSet

보통 SELECT 쿼리의 결과가 순서대로 들어간다.
ex. SELECT member_id, money -> member_id , money 라는 이름으로 데이터가 저장
ex. SELECT * -> 테이블의 모든 컬럼을 다 저장


ResultSet 내부에 있는 커서( cursor )를 이동해서 다음 데이터를 조회할 수 있다.

  • rs.next()호출시 커서가 다음으로 이동한다.
  • 최초의 커서는 데이터를 가리키고 있지 않기 때문에 rs.next() 를 최초 한번은 호출해야 데이터를 조회할 수 있다.
  • rs.next() 의 결과가 true 면 커서의 이동 결과 데이터가 있다는 뜻이다.
  • rs.next() 의 결과가 false 면 더이상 커서가 가리키는 데이터가 없다는 뜻이다.

5. 리소스 정리

쿼리를 실행하고 나면 리소스를 정리해야 한다.

  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 을 먼저 획득하고 Connection 을 통해 PreparedStatement 를 만들었기 때문에 리소스를 반환할 때는 PreparedStatement 를 먼저 종료하고, 그 다음에 Connection 을 종료하면 된다.
  • 리소스 정리는 꼭! 해주어야 한다. 따라서 예외가 발생하든, 하지 않든 항상 수행되어야 하므로 finally 구문에 주의해서 작성해야한다. 만약 이 부분을 놓치게 되면 커넥션이 끊어지지 않고 계속 유지되는 문제가 발생할 수 있다. 이런 것을 리소스 누수라고 하는데, 결과적으로 커넥션 부족으로 장애가 발생할 수 있다.

이 글은 인프런 김영한님의 스프링 DB 1편의 [섹션1. JDBC 이해]를 기반으로 정리한 글입니다.

profile
오잉이라네 오잉이라네 오잉이라네 ~

0개의 댓글