///////
Search
📺

userDao ~~

JDBC연동을 위해 알아야 할 내용들

Connection
PreparedStatement
ResultSet

add() & findById()

Factory, Spring 적용 후 코드

public void add(User user) { Map<String, String> env = System.getenv(); try { // DB접속 (ex sql workbeanch실행) Connection c = cm.makeConnection(); // Query문 작성 PreparedStatement pstmt = c.prepareStatement("INSERT INTO users(id, name, password) VALUES(?,?,?);"); // 매개변수로 받아온 user 객체를 PreparedStatement에 pstmt.setString(1, user.getId()); pstmt.setString(2, user.getName()); pstmt.setString(3, user.getPassword()); // Query문 실행 pstmt.executeUpdate(); pstmt.close(); c.close(); } catch (SQLException e) { throw new RuntimeException(e); } } public User findById(String id) { Map<String, String> env = System.getenv(); Connection c; try { // DB접속 (ex sql workbeanch실행) c = cm.makeConnection(); // Query문 작성 PreparedStatement pstmt = c.prepareStatement("SELECT * FROM users WHERE id = ?"); pstmt.setString(1, id); // Query문 실행 ResultSet rs = pstmt.executeQuery(); rs.next(); User user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password")); rs.close(); pstmt.close(); c.close(); return user; } catch (SQLException e) { throw new RuntimeException(e); } }
Java
복사
코드 이해하기
위의 코드에서 발생할 수 있는 문제점은? 만약, Connection 연결은 됐는데, Query문을 잘못 작성하여 PreparedStatement가 생성되지 않았다면 SQLException으로 인해 catch문으로 넘어감 Connection과 PreparedStatement의 close() 즉, 자원의 반환이 이루어지지 않아 Connection Pool에 반환되지 않은 자원들이 계속 쌓이게 됨

Refactoring (finally)

public void add(User user) { Map<String, String> env = System.getenv(); try { // DB접속 (ex sql workbeanch실행) Connection c = cm.makeConnection(); // Query문 작성 PreparedStatement pstmt = c.prepareStatement("INSERT INTO users(id, name, password) VALUES(?,?,?);"); // 매개변수로 받아온 user 객체를 PreparedStatement에 pstmt.setString(1, user.getId()); pstmt.setString(2, user.getName()); pstmt.setString(3, user.getPassword()); // Query문 실행 pstmt.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } finally { // ✨ 확인할 곳 pstmt.close(); c.close(); } } public User findById(String id) { Map<String, String> env = System.getenv(); Connection c; try { // DB접속 (ex sql workbeanch실행) c = cm.makeConnection(); // Query문 작성 PreparedStatement pstmt = c.prepareStatement("SELECT * FROM users WHERE id = ?"); pstmt.setString(1, id); // Query문 실행 ResultSet rs = pstmt.executeQuery(); rs.next(); User user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password")); return user; } catch (SQLException e) { throw new RuntimeException(e); }finally { // ✨ 확인할 곳 rs.close(); pstmt.close(); c.close(); } }
Java
복사
UserDao.java
자원을 반환하는 코드를 모두 finally 를 이용하여 예외가 발생하여도 반드시 수행할 수 있도록 함
자원을 반환할 때는 반드시 객체를 생성한 역순으로 반환해야 함
리팩토링한 코드에서 발생할 수 있는 문제 만약 Connection 연결이 되었고 PreparedStatement에서 문제가 생긴 경우를 생각해보자. Connection 객체 생성은 되었지만 PreparedStatement의 객체는 생성되지 않았다. 그럼 아무리 finallyclose() 를 작성하였다고 해도 반환하고 싶어도 반환할 자원이 없다.

Refactoring2 (조건문, try~catch)

public void add(User user) { Map<String, String> env = System.getenv(); Connection c = null; PreparedStatement pstmt = null; try { // DB접속 (ex sql workbeanch실행) c = cm.makeConnection(); // Query문 작성 pstmt = c.prepareStatement("INSERT INTO users(id, name, password) VALUES(?,?,?);"); // 매개변수로 받아온 user 객체를 PreparedStatement에 pstmt.setString(1, user.getId()); pstmt.setString(2, user.getName()); pstmt.setString(3, user.getPassword()); // Query문 실행 pstmt.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } finally { // ✨ 확인할 곳 if(pstmt != null) { try { pstmt.close(); }catch(SQLException e) { e.printStackTrace(); } } if(c != null) { try { c.close(); }catch(SQLException e) { e.printStackTrace(); } } } } public User findById(String id) { Map<String, String> env = System.getenv(); Connection c = null; PreparedStatement pstmt = null; ResultSet rs = null; try { // DB접속 (ex sql workbeanch실행) c = cm.makeConnection(); // Query문 작성 pstmt = c.prepareStatement("SELECT * FROM users WHERE id = ?"); pstmt.setString(1, id); // Query문 실행 rs = pstmt.executeQuery(); rs.next(); User user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password")); return user; } catch (SQLException e) { throw new RuntimeException(e); }finally { // ✨ 확인할 곳 if(rs != null) { try { rs.close(); }catch(SQLException e) { e.printStackTrace(); } } if(pstmt!= null) { try { pstmt.close(); }catch(SQLException e) { e.printStackTrace(); } } if(c != null) { try { c.close(); }catch(SQLException e) { e.printStackTrace(); } } } }
Java
복사
UserDao.java
다음과 같이 Refactoring을 함으로써 해당 객체가 null이 아닐 때(생성이 되었을 때)에만 자원을 반환해 줄 수 있도록 함 각 자원을 반환해줄 때도 예외가 발생할 수 있으므로 try~catch문을 활용하여 예외 처리를 해주어야 함

deleteAll()

deleteAll() 작성
public void deleteAll() throws SQLException { Connection c = makeConnection(); PreparedStatement ps= c.prepareStatement("DELETE FROM users"); ps.executeUpdate(); ps.close(); c.close(); }
Java
복사
statementdelete문을 저장하고 업데이트 합니다.
DELETE vs TRUNCATE
DELETE : 조건이 없으면 TRUNCATE와 동일하게 모든 데이터가 삭제되고 테이블 스키마만 남는 형태가 됩니다.
차이점 : DELETE명령어는 데이터 복구가 가능하고, 로그가 남습니다.
1번과 같은 코드는 문제점이 있습니다.
ConnectionStatement를 생성할 때 오류가 발생했다면?
close() 하는 코드가 실행되지 않고 중간에 멈춰버립니다.
로컬 환경에선 문제가 없지만 서버에서 실행된다면 서버가 다운될 수도 있습니다.
public void deleteAll() throws SQLException { Connection c = null; PreparedStatement ps = null; try { c = makeConnection(); ps = c.prepareStatement("DELETE FROM users"); ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } finally { if (ps != null) { try{ ps.close(); }catch (SQLException e){ } } if (c != null) { try{ c.close(); }catch (SQLException e){ } } } }
Java
복사
다음과 같이 try-catch 문으로 감싸주면 ConnectionStatement를 생성할 때 문제가 발생해도 close()문이 실행됩니다.

getCount()

getCount() 작성
public int getCount1() throws SQLException { Connection conn = makeConnection(); PreparedStatement ps = conn.prepareStatement("SELECT COUNT(ID) FROM users"); ResultSet rs = ps.executeQuery(); rs.next(); int s = Integer.parseInt(rs.getString("COUNT(ID)")); conn.close(); ps.close(); rs.close(); return s; }
Java
복사
getCount() 는 모든 레코드의 갯수를 세어서 반환해주는 메소드입니다.
DB에서 데이터를 가져오기 때문에 ResultSet을 사용해주어야 합니다.
deleteAll() 과 마찬가지로 close() 가 언제나 작동되도록 리펙터링해줍니다.
public int getCount() throws SQLException { Connection conn = null; ResultSet rs = null; PreparedStatement ps = null; int s; try { conn = makeConnection(); ps = conn.prepareStatement("SELECT COUNT(ID) FROM users"); rs = ps.executeQuery(); rs.next(); s = Integer.parseInt(rs.getString("COUNT(ID)")); } catch (SQLException e) { throw new RuntimeException(e); } finally { if (ps != null) { try { ps.close(); } catch (SQLException e) { } } if (conn != null) { try { conn.close(); } catch (SQLException e) { } } if (rs != null) { try { rs.close(); } catch (SQLException e) { } } } return s; }
Java
복사
2번코드는 문제는 없지만 try-catch를 3번이나 실행해야 하기 때문에 코드가 상당히 길어집니다.
다음과 같은 방법으로 개선할 수 있습니다.
public int getCount() throws SQLException { Connection conn = null; ResultSet rs = null; PreparedStatement ps = null; int s; try { conn = makeConnection(); ps = conn.prepareStatement("SELECT COUNT(ID) FROM users"); rs = ps.executeQuery(); rs.next(); s = Integer.parseInt(rs.getString("COUNT(ID)")); } catch (SQLException e) { throw new RuntimeException(e); } finally { close(conn, ps, rs); } return s; }
Java
복사
close() 함수는 getCount 말고도 다른 모든 메소드에서 사용할 수 있습니다. 보다 간결하게 코딩을 할 수 있습니다. close()를 구현해보겠습니다.
private void close(AutoCloseable... autoCloseables){ for (AutoCloseable ac : autoCloseables) { if (ac != null) { try { ac.close(); } catch (Exception e) { throw new RuntimeException(e) } } } }
Java
복사
파라미터에 있는 은 매개변수로 얼만큼 오든 알아서 받아줍니다.
Connection, ResultSet, Statement 모두 같은 인터페이스를 상속 받기 때문에 다음과 같이 AutoCloseable로 넘겨줄 수 있습니다.