토비의 스프링 3-1 다시 보는 초난감 DAO(예외처리)- 김희정
OCP : 확장에는 열려 있고 변경에는 닫혀 있다.
>> 변화의 특성이 다른 부분을 구분해주고, 각각 다른 목적과 다른 이유에 의해 다른 시점에 독립적으로 변경될 수 있는 효율적인 구조만들어줌
템플릿이란?
•
변화하는 성질이 다른 코드 중에서 변경 x, 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질과 독립 시켜 효과적으로 활용하려는 기법(변화하는 부분과 유지되는 부분을 독립시킴)
UserDao의 문제점 : 예외상황에 대한 처리
3.1.1 : 예외처리 기능을 갖춘 DAO
deleteAll() 예외처리
public void deleteAll() throws SQLException {
Connection conn = connectionMaker.makeConnection();
PreparedStatement ps = conn.prepareStatement("DELETE from users");
ps.executeUpdate();
ps.close();
conn.close();
}
Java
복사
문제점 : PreparedStatement 처리 중 예외 발생 >> 메소드 끝까지 실행 안하고 끝남 >> PreparedStatement , Connection close() 실행x >> 리소스 반환 x
리소스 반환 안하는 게 왜 문제?
•
서버에서 띄우면 문제 >> 서버 다운
•
1초에 10번 요청 >> 1시간 3600건의 Call 발생 >> PreparedStatement , Connection 이 3600건이 생기는 것 >> 커넥션 풀에 여유 없어지고 리소스 부족 >> 서버 다운
해결 : try/catch/finally로 예외상황에서도 반환하도록
public void deleteAll() throws SQLException {
Connection conn = null;
PreparedStatement ps = null
try {
conn = connectionMaker.makeConnection();
ps = conn.prepareStatement("DELETE from users");
ps.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally { //에러가 나도 실행 >> close
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
}
}
}
}
Java
복사
finally에 그냥 ps.close(), conn.close()하면 안되나?
•
안됨. null상태의 변수에 close()를 호출하면 NullPointerException 발생
finally안 ps.close(), conn.close()도 try-catch를 꼭해야하나?
•
try/catch 블록 없이 ps.close() 예외발생하면 c.close()가 실행되지 않고 메소드 빠져나감.
getCount() 예외처리
•
리소스 : ResultSet 추가
public int getCount() throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = connectionMaker.makeConnection();
ps = conn.prepareStatement("SELECT count(*) FROM users");
rs = ps.executeQuery();
rs.next();
return rs.getInt(1);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//close
//close()는 만들어진 순서 반대로
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
}
}
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
}
}
}
}
Java
복사
Spring-김지영
deleteAll() · getCount() 구현 & 테스트
public void deleteAll() throws SQLException, ClassNotFoundException {
Connection c = null;
PreparedStatement ps = null;
try {
c = connectionMaker.makeConnection();
ps = c.prepareStatement("DELETE FROM userdao.users");
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
}
}
if (c != null) {
try {
c.close();
} catch (SQLException e) {
}
}
}
}
Java
복사
deleteAll() 메서드는 테이블의 모든 데이터를 삭제하도록 하는 메서드 입니다.
deleteAll(), getCount() 예외 처리
•
connection, PreparedStatement 사용할 때 에러가 발생할 수 있다.
•
그래서 ps.close(), c.close() 메서드 부분을 처리해주지 않으면 리소스 반환이 되지 않아 서버가 다운될 수 있다고 한다.
•
꼭 try/catch/finally 으로 예외를 잡아야 한다.
리소스를 정상적으로 반환하지 못하는 경우 서버가 다운되는 치명적인 상황이 발생할 수 있기 때문에, 공유 리소스를 사용하는 경우 예외처리를 잘 해주어야 합니다.
public int getCount() throws SQLException, ClassNotFoundException {
Connection c = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
c= connectionMaker.makeConnection();
ps = c.prepareStatement("SELECT COUNT(*) as count FROM userdao.users");
rs = ps.executeQuery();
rs.next();
return rs.getInt("count");
} catch (SQLException e) {
throw e;
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
}
}
if (c != null) {
try {
c.close();
} catch (SQLException e) {
}
}
}
Java
복사
@Test
@DisplayName("deleteAll & getCount 테스트")
void deleteAndCountTest() throws SQLException, ClassNotFoundException {
userDao.add(new User("1", "JiYeong", "6415"));
userDao.add(new User("2", "JiYeong", "6415"));
//데이터가 1개만 들어있는 상태에서, getCount를 했을 때, 값은 1일 것
assertThat(userDao.getCount()).isEqualTo(2);
//deleteAll() 메서드 실행
userDao.deleteAll();
//데이터가 전부 삭제된 후에 Count를 하게되면 0 값일 것
assertThat(userDao.getCount()).isEqualTo(0);
}
Java
복사
@BeforeEach
void RunBeforeDelete() throws SQLException, ClassNotFoundException {
userDao.deleteAll();
}
Java
복사
select() 리팩토링
public User getById(String id) throws SQLException, ClassNotFoundException {
Connection c = connectionMaker.makeConnection();
PreparedStatement ps = c.prepareStatement("SELECT * FROM userdao.users where id = ?");
ps.setString(1, id);
ResultSet rs = ps.executeQuery();
User user = null;
if (rs.next()) {
user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
}
rs.close();
ps.close();
c.close();
if (user == null) throw new NullPointerException();
return user;
}
Java
복사
select 역할을 하는 메서드 또한, 리소스를 안정적으로 다뤄주도록 리팩토링 할 수 있습니다.
@Test
void 리팩토링후add테스트(){
User user = null;
Assertions.assertThrows(NullPointerException.class,()->userDao.add(user));
}
Java
복사