프로그래밍언어/Java

[Java] try-catch-final 보다 try-with-resources를 이용하자

멍토 2022. 3. 25.

try - catch - finally

public void insert(User user) {
      final String sql = "insert into users (account, password, email) values (?, ?, ?)";

      Connection conn = null;
      PreparedStatement pstmt = null;
      try {
          conn = dataSource.getConnection();
          pstmt = conn.prepareStatement(sql);

          pstmt.setString(1, user.getAccount());
          pstmt.setString(2, user.getPassword());
          pstmt.setString(3, user.getEmail());
          pstmt.executeUpdate();
      } catch (SQLException e) {
          log.error(e.getMessage(), e);
          throw new RuntimeException(e);
      } finally {
          try {
              if (pstmt != null) {
                  pstmt.close();
              }
          } catch (SQLException ignored) {}

          try {
              if (conn != null) {
                  conn.close();
              }
          } catch (SQLException ignored) {}
      }
  }

위의 형식은 일반적으로 볼 수 있는 try-catch-finally 구조이다.

이런 형식으로 작성시 finally에서 null을 체크하고 반환을 해야 하는 반복적인 코드가 나오게 된다.

그렇다면 위와 같은 코드들의 중복을 줄일 수 없을까?

첫번째로 간단하게 생각해 볼 수 있는것은 메서드 분리이다.

public void insert(User user) {
    final String sql = "insert into users (account, password, email) values (?, ?, ?)";

    Connection conn = null;
    PreparedStatement pstmt = null;
    try {
        conn = dataSource.getConnection();
        pstmt = conn.prepareStatement(sql);

        pstmt.setString(1, user.getAccount());
        pstmt.setString(2, user.getPassword());
        pstmt.setString(3, user.getEmail());
        pstmt.executeUpdate();
    } catch (SQLException e) {
        log.error(e.getMessage(), e);
        throw new RuntimeException(e);
    } finally {
        closes(conn, pstmt);
    }
}

private void closes(Connection conn, PreparedStatement pstmt) {
    try {
        if (pstmt != null) {
            pstmt.close();
        }
    } catch (SQLException ignored) {
    }

    try {
        if (conn != null) {
            conn.close();
        }
    } catch (SQLException ignored) {
    }
}

여기서 조금 더 분리하면 아래처럼 가능하다.

public void insert(User user) {
    final String sql = "insert into users (account, password, email) values (?, ?, ?)";

    Connection conn = null;
    PreparedStatement pstmt = null;
    try {
        conn = dataSource.getConnection();
        pstmt = conn.prepareStatement(sql);

        pstmt.setString(1, user.getAccount());
        pstmt.setString(2, user.getPassword());
        pstmt.setString(3, user.getEmail());
        pstmt.executeUpdate();
    } catch (SQLException e) {
        log.error(e.getMessage(), e);
        throw new RuntimeException(e);
    } finally {
        closes(conn, pstmt);
    }
}

private void closes(Connection conn, PreparedStatement pstmt) {
    preparedStatementClose(pstmt);
    connectionClose(conn);
}

private void connectionClose(Connection conn) {
    try {
        if (conn != null) {
            conn.close();
        }
    } catch (SQLException ignored) {
    }
}

private void preparedStatementClose(PreparedStatement pstmt) {
    try {
        if (pstmt != null) {
            pstmt.close();
        }
    } catch (SQLException ignored) {
    }
}

close 하는 부분이 중복처럼 보이기때문에 메서드를 하나로 통일하면 아래와 같다.

public void insert(User user) {
    final String sql = "insert into users (account, password, email) values (?, ?, ?)";

    Connection conn = null;
    PreparedStatement pstmt = null;
    try {
        conn = dataSource.getConnection();
        pstmt = conn.prepareStatement(sq

        pstmt.setString(1, user.getAccount());
        pstmt.setString(2, user.getPassword());
        pstmt.setString(3, user.getEmail());
        pstmt.executeUpdate();
    } catch (SQLException e) {
        log.error(e.getMessage(), e);
        throw new RuntimeException(e);
    } finally {
        closes(conn, pstmt);
    }
}

private void closes(AutoCloseable... autoCloseables) {
    for (AutoCloseable autoCloseable: autoCloseables) {
        close(autoCloseable);
    }
}

private void close(AutoCloseable conn) {
    try {
        if (conn != null) {
            conn.close();
        }
    } catch (Exception e) { }
}

 

try-with-resources

중복을 많이 줄이고 다른곳에서도 재사용이 가능해졌지만 finally에서 신경을 계속써야하는 부분은 변함이 없다. 그리고 이러한 부분에서 개발자들의 실수가 발생해 메모리 릭이 발생한다.

이펙티브 자바에 적힌바에 의한다면 2007년 자바의 라이브러리는 1/3정도만 제대로 close 했다고 한다.

이러한걸 한번에 해결해주는 문법이 있다.

try-with-resources 라는 문법이다.

public void example() {

    try(여기서 자원할당) {
            //코드작성
    } catch(Exception e) {
            // 에러핸들링
    }
}

try뒤에있는 ()안에 있는 자원들을 자동으로 해제시켜준다.

public void insert(User user) {
    final String sql = "insert into users (account, password, email) values (?, ?, ?)";

    try(Connection conn = dataSource.getConnection();
        PreparedStatement pstmt = conn.prepareStatement(sql)) {

        pstmt.setString(1, user.getAccount());
        pstmt.setString(2, user.getPassword());
        pstmt.setString(3, user.getEmail());
        pstmt.executeUpdate();
    } catch (SQLException e) {
        log.error(e.getMessage(), e);
        throw new RuntimeException(e);
    }
}

위와 같이 작성하면 에러가 발생하기 쉬워지는 null이 나오지 않고 자원도 신경쓰지 않아도 자동으로 해제시켜준다.

즉, 개발자의 실수를 줄여주고 간결한 코드를 볼 수 있도록 해준다.

close 되는건 Autoclosable 인터페이스를 구현한 객체들만 종료된다.

지원은 자바7 버전부터 지원이 되기 시작했다.

 

결론 : try - with - resources 를 사용하자

이펙티브자바 아이템9에 자세한 설명이 있다.

댓글

💲 광고입니다.