3.1) 다시 보는 초난감 DAO
예외처리 기능을 갖춘 DAO
- UserDao에는 예외상황에 대한 처리에 대한 심각한 문제점이 남아있다.
- DB 커넥션이라는 제한적인 리소스를 공유해 사용하는 서버에서 동작하는 JDBC 코드에는 예외처리를 반드시 지켜야 한다.
- 정상적인 JDBC 코드의 흐름을 따르지 않고 중간에 어떤 이유로든 예외가 발생했을 경우에도
사용한 리소스를 반드시 반환하도록 만들어야만 시스템에 심각한 문제를 일으키지 않기 때문이다.
- UserDao의 수정 기능을 하는 deleteAll() 메소드를 살펴보자.
- 이 메소드에서는 Connection과 PreparedStatement라는 두 개의 공유 리소스를 가져와서 사용하고
정상적으로 처리될 경우 메소드를 마치기 전에 각각 close()를 호출해 리소스를 반환한다. - 하지만 PreparedStatement를 처리하는 중에 예외가 발생하면 제대로 리소스가 반환되지 않을 수 있다.
- 일반적으로 서버에서는 제한된 개수의 DB 커넥션을 만들어 재사용 가능한 풀로 관리하지만
이런 식으로 오류가 날 때마다 미처 close()를 통해 반환되지 못한 Connection이 계속 쌓이면
어느 순간에 커넥션 풀에 여유가 없어지고 리소스가 모자란다는 심각한 오류를 내며 서버가 중단될 수 있다.
- 이 메소드에서는 Connection과 PreparedStatement라는 두 개의 공유 리소스를 가져와서 사용하고
- 예외상황에서도 리소스를 제대로 반환할 수 있도록 try/catch/finally를 적용해보자.
- JDBC 코드에서는 어떤 상황에서도 가져온 리소스를 반환하도록 try/catch/finally 구문 사용을 권장하고 있다.
- finally는 try 블록을 수행한 후 예외가 발생하든 정상적으로 처리되든 상관없이 반드시 실행되는 코드를 넣을 때 사용한다.
- 이때 어느 시점에 예외가 발생했는지에 따라 close()를 사용할 수 있는 변수가 달라질 수 있기 때문에 finally에서는 반드시
c(Conneciton)와 ps(PreparedStatement)가 null이 아닌지 먼저 확인한 후 close() 메소드를 호출해야 한다. - 문제는 이 close()도 SQLException이 발생할 수 있는 메소드이므로 try/catch 문으로 처리해줘야 한다.
/**
* JDBC를 이용한 등록과 조회 기능이 있는 UserDao 클래스
*/
public class UserDao {
// UserDao에 주입될 의존 오브젝트의 타입을 ConnectionMaker에서 DataSource로 변경한다.
private DataSource dataSource;
// DataSource 인터페이스 적용
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/* 새로운 사용자를 생성 */
// JDBC API가 만들어내는 예외를 잡아서 직접 처리하거나, 메소드에 throws를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다.
public void add(User user) throws SQLException {
// DB 연결을 위한 Connection을 가져온다.
Connection c = dataSource.getConnection();
// SQL을 담은 Statement를 만든다.
PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) values(?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
// 만들어진 Statement를 실행한다.
ps.executeUpdate();
ps.close();
c.close();
}
/* 아이디를 가지고 사용자 정보 읽어오기 */
public User get(String id) throws SQLException {
Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
ps.setString(1, id);
// SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 오브젝트에 옮겨준다.
ResultSet rs = ps.executeQuery();
// User는 null 상태로 초기화해놓는다.
User user = null;
// id를 조건으로 한 쿼리의 결과가 있으면 User 오브젝트를 만들고 값을 넣어준다.
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();
// 결과가 없으면 User는 null 상태 그대로일 것이므로 이를 확인해서 예외를 던져준다.
if (user == null) throw new EmptyResultDataAccessException(1);
return user;
}
/* 모든 사용자 삭제하기 */
public void deleteAll() throws SQLException {
// Connection과 PreparedStatement라는 두 개의 공유 리소스를 가져와서 사용한다.
// 이후 정상적으로 처리될 경우 메소드를 마치기 전에 각각 close()를 호출해 리소스를 반환한다.
Connection c = null;
PreparedStatement ps = null;
// 어떤 상황에서도 가져온 리소스를 반환하도록 try/catch/finally 구문 사용
try {
// 예외가 발생할 가능성이 있는 코드를 모두 try 블록으로 묶어준다.
c = dataSource.getConnection();
ps = c.prepareStatement("delete from users");
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
if (ps != null) {
// close()도 SQLException이 발생할 수 있는 메소드이므로 try/catch 문으로 처리한다.
try {
ps.close();
} catch (SQLException e) {
}
}
if (c != null) {
try {
c.close();
} catch (SQLException e) {
}
}
}
}
/* 사용자 테이블의 레코드 개수 읽어오기 */
public int getCount() throws SQLException {
Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("select count(*) from users");
ResultSet rs = ps.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
ps.close();
c.close();
return count;
}
}
- 조회 기능을 하는 getCount() 메소드를 살펴보자.
- 조회를 위한 JDBC는 Connection, PreparedStatement 외에도 ResultSet이 추가되므로 좀 더 복잡해진다.
- ResultSet도 반환해야 하는 리소스이므로 예외상황에서도 ResultSet이 close() 메소드가 반드시 호출되도록 만들면 된다.
/**
* JDBC를 이용한 등록과 조회 기능이 있는 UserDao 클래스
*/
public class UserDao {
// UserDao에 주입될 의존 오브젝트의 타입을 ConnectionMaker에서 DataSource로 변경한다.
private DataSource dataSource;
// DataSource 인터페이스 적용
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/* 새로운 사용자를 생성 */
// JDBC API가 만들어내는 예외를 잡아서 직접 처리하거나, 메소드에 throws를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다.
public void add(User user) throws SQLException {
// DB 연결을 위한 Connection을 가져온다.
Connection c = dataSource.getConnection();
// SQL을 담은 Statement를 만든다.
PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) values(?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
// 만들어진 Statement를 실행한다.
ps.executeUpdate();
ps.close();
c.close();
}
/* 아이디를 가지고 사용자 정보 읽어오기 */
public User get(String id) throws SQLException {
Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
ps.setString(1, id);
// SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 오브젝트에 옮겨준다.
ResultSet rs = ps.executeQuery();
// User는 null 상태로 초기화해놓는다.
User user = null;
// id를 조건으로 한 쿼리의 결과가 있으면 User 오브젝트를 만들고 값을 넣어준다.
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();
// 결과가 없으면 User는 null 상태 그대로일 것이므로 이를 확인해서 예외를 던져준다.
if (user == null) throw new EmptyResultDataAccessException(1);
return user;
}
/* 모든 사용자 삭제하기 */
public void deleteAll() throws SQLException {
// Connection과 PreparedStatement라는 두 개의 공유 리소스를 가져와서 사용한다.
// 이후 정상적으로 처리될 경우 메소드를 마치기 전에 각각 close()를 호출해 리소스를 반환한다.
Connection c = null;
PreparedStatement ps = null;
// 어떤 상황에서도 가져온 리소스를 반환하도록 try/catch/finally 구문 사용
try {
// 예외가 발생할 가능성이 있는 코드를 모두 try 블록으로 묶어준다.
c = dataSource.getConnection();
ps = c.prepareStatement("delete from users");
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
if (ps != null) {
// close()도 SQLException이 발생할 수 있는 메소드이므로 try/catch 문으로 처리한다.
try {
ps.close();
} catch (SQLException e) {
}
}
if (c != null) {
try {
c.close();
} catch (SQLException e) {
}
}
}
}
/* 사용자 테이블의 레코드 개수 읽어오기 */
public int getCount() throws SQLException {
Connection c = null;
PreparedStatement ps = null;
ResultSet rs = null;
// ResultSet도 다양한 SQLException이 발생할 수 있는 코드이므로 try 블록 안에 둬야 한다.
try {
c = dataSource.getConnection();
ps = c.prepareStatement("select count(*) from users");
rs = ps.executeQuery();
rs.next();
return rs.getInt(1);
} 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) {
}
}
}
}
}
- UserDao의 모든 메소드에 동일한 방식으로 try/catch/finally 블록을 적용하는 작업을 마쳤으면
UserDaoTest 테스트를 수행해보고 이상이 없는지 확인한다.- 이제 서버환경에서도 안정적으로 수행될 수 있으면서 DB 연결 기능을 자유롭게 확장할 수 있는 이상적인 DAO가 완성됐다.
- 하지만! 여전히 뭔가 아쉬움이 남아 있다.
'Java-Spring > 토비의 스프링 3.1' 카테고리의 다른 글
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 템플릿 (3) (0) | 2023.09.28 |
---|---|
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 템플릿 (2) (0) | 2023.09.28 |
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 템플릿 (0) (0) | 2023.09.28 |
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 테스트 (5) (0) | 2023.09.23 |
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 테스트 (4) (0) | 2023.09.23 |
3.1) 다시 보는 초난감 DAO
예외처리 기능을 갖춘 DAO
- UserDao에는 예외상황에 대한 처리에 대한 심각한 문제점이 남아있다.
- DB 커넥션이라는 제한적인 리소스를 공유해 사용하는 서버에서 동작하는 JDBC 코드에는 예외처리를 반드시 지켜야 한다.
- 정상적인 JDBC 코드의 흐름을 따르지 않고 중간에 어떤 이유로든 예외가 발생했을 경우에도
사용한 리소스를 반드시 반환하도록 만들어야만 시스템에 심각한 문제를 일으키지 않기 때문이다.
- UserDao의 수정 기능을 하는 deleteAll() 메소드를 살펴보자.
- 이 메소드에서는 Connection과 PreparedStatement라는 두 개의 공유 리소스를 가져와서 사용하고
정상적으로 처리될 경우 메소드를 마치기 전에 각각 close()를 호출해 리소스를 반환한다. - 하지만 PreparedStatement를 처리하는 중에 예외가 발생하면 제대로 리소스가 반환되지 않을 수 있다.
- 일반적으로 서버에서는 제한된 개수의 DB 커넥션을 만들어 재사용 가능한 풀로 관리하지만
이런 식으로 오류가 날 때마다 미처 close()를 통해 반환되지 못한 Connection이 계속 쌓이면
어느 순간에 커넥션 풀에 여유가 없어지고 리소스가 모자란다는 심각한 오류를 내며 서버가 중단될 수 있다.
- 이 메소드에서는 Connection과 PreparedStatement라는 두 개의 공유 리소스를 가져와서 사용하고
- 예외상황에서도 리소스를 제대로 반환할 수 있도록 try/catch/finally를 적용해보자.
- JDBC 코드에서는 어떤 상황에서도 가져온 리소스를 반환하도록 try/catch/finally 구문 사용을 권장하고 있다.
- finally는 try 블록을 수행한 후 예외가 발생하든 정상적으로 처리되든 상관없이 반드시 실행되는 코드를 넣을 때 사용한다.
- 이때 어느 시점에 예외가 발생했는지에 따라 close()를 사용할 수 있는 변수가 달라질 수 있기 때문에 finally에서는 반드시
c(Conneciton)와 ps(PreparedStatement)가 null이 아닌지 먼저 확인한 후 close() 메소드를 호출해야 한다. - 문제는 이 close()도 SQLException이 발생할 수 있는 메소드이므로 try/catch 문으로 처리해줘야 한다.
/**
* JDBC를 이용한 등록과 조회 기능이 있는 UserDao 클래스
*/
public class UserDao {
// UserDao에 주입될 의존 오브젝트의 타입을 ConnectionMaker에서 DataSource로 변경한다.
private DataSource dataSource;
// DataSource 인터페이스 적용
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/* 새로운 사용자를 생성 */
// JDBC API가 만들어내는 예외를 잡아서 직접 처리하거나, 메소드에 throws를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다.
public void add(User user) throws SQLException {
// DB 연결을 위한 Connection을 가져온다.
Connection c = dataSource.getConnection();
// SQL을 담은 Statement를 만든다.
PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) values(?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
// 만들어진 Statement를 실행한다.
ps.executeUpdate();
ps.close();
c.close();
}
/* 아이디를 가지고 사용자 정보 읽어오기 */
public User get(String id) throws SQLException {
Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
ps.setString(1, id);
// SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 오브젝트에 옮겨준다.
ResultSet rs = ps.executeQuery();
// User는 null 상태로 초기화해놓는다.
User user = null;
// id를 조건으로 한 쿼리의 결과가 있으면 User 오브젝트를 만들고 값을 넣어준다.
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();
// 결과가 없으면 User는 null 상태 그대로일 것이므로 이를 확인해서 예외를 던져준다.
if (user == null) throw new EmptyResultDataAccessException(1);
return user;
}
/* 모든 사용자 삭제하기 */
public void deleteAll() throws SQLException {
// Connection과 PreparedStatement라는 두 개의 공유 리소스를 가져와서 사용한다.
// 이후 정상적으로 처리될 경우 메소드를 마치기 전에 각각 close()를 호출해 리소스를 반환한다.
Connection c = null;
PreparedStatement ps = null;
// 어떤 상황에서도 가져온 리소스를 반환하도록 try/catch/finally 구문 사용
try {
// 예외가 발생할 가능성이 있는 코드를 모두 try 블록으로 묶어준다.
c = dataSource.getConnection();
ps = c.prepareStatement("delete from users");
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
if (ps != null) {
// close()도 SQLException이 발생할 수 있는 메소드이므로 try/catch 문으로 처리한다.
try {
ps.close();
} catch (SQLException e) {
}
}
if (c != null) {
try {
c.close();
} catch (SQLException e) {
}
}
}
}
/* 사용자 테이블의 레코드 개수 읽어오기 */
public int getCount() throws SQLException {
Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("select count(*) from users");
ResultSet rs = ps.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
ps.close();
c.close();
return count;
}
}
- 조회 기능을 하는 getCount() 메소드를 살펴보자.
- 조회를 위한 JDBC는 Connection, PreparedStatement 외에도 ResultSet이 추가되므로 좀 더 복잡해진다.
- ResultSet도 반환해야 하는 리소스이므로 예외상황에서도 ResultSet이 close() 메소드가 반드시 호출되도록 만들면 된다.
/**
* JDBC를 이용한 등록과 조회 기능이 있는 UserDao 클래스
*/
public class UserDao {
// UserDao에 주입될 의존 오브젝트의 타입을 ConnectionMaker에서 DataSource로 변경한다.
private DataSource dataSource;
// DataSource 인터페이스 적용
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/* 새로운 사용자를 생성 */
// JDBC API가 만들어내는 예외를 잡아서 직접 처리하거나, 메소드에 throws를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다.
public void add(User user) throws SQLException {
// DB 연결을 위한 Connection을 가져온다.
Connection c = dataSource.getConnection();
// SQL을 담은 Statement를 만든다.
PreparedStatement ps = c.prepareStatement("insert into users(id, name, password) values(?,?,?)");
ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
// 만들어진 Statement를 실행한다.
ps.executeUpdate();
ps.close();
c.close();
}
/* 아이디를 가지고 사용자 정보 읽어오기 */
public User get(String id) throws SQLException {
Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
ps.setString(1, id);
// SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 오브젝트에 옮겨준다.
ResultSet rs = ps.executeQuery();
// User는 null 상태로 초기화해놓는다.
User user = null;
// id를 조건으로 한 쿼리의 결과가 있으면 User 오브젝트를 만들고 값을 넣어준다.
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();
// 결과가 없으면 User는 null 상태 그대로일 것이므로 이를 확인해서 예외를 던져준다.
if (user == null) throw new EmptyResultDataAccessException(1);
return user;
}
/* 모든 사용자 삭제하기 */
public void deleteAll() throws SQLException {
// Connection과 PreparedStatement라는 두 개의 공유 리소스를 가져와서 사용한다.
// 이후 정상적으로 처리될 경우 메소드를 마치기 전에 각각 close()를 호출해 리소스를 반환한다.
Connection c = null;
PreparedStatement ps = null;
// 어떤 상황에서도 가져온 리소스를 반환하도록 try/catch/finally 구문 사용
try {
// 예외가 발생할 가능성이 있는 코드를 모두 try 블록으로 묶어준다.
c = dataSource.getConnection();
ps = c.prepareStatement("delete from users");
ps.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
if (ps != null) {
// close()도 SQLException이 발생할 수 있는 메소드이므로 try/catch 문으로 처리한다.
try {
ps.close();
} catch (SQLException e) {
}
}
if (c != null) {
try {
c.close();
} catch (SQLException e) {
}
}
}
}
/* 사용자 테이블의 레코드 개수 읽어오기 */
public int getCount() throws SQLException {
Connection c = null;
PreparedStatement ps = null;
ResultSet rs = null;
// ResultSet도 다양한 SQLException이 발생할 수 있는 코드이므로 try 블록 안에 둬야 한다.
try {
c = dataSource.getConnection();
ps = c.prepareStatement("select count(*) from users");
rs = ps.executeQuery();
rs.next();
return rs.getInt(1);
} 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) {
}
}
}
}
}
- UserDao의 모든 메소드에 동일한 방식으로 try/catch/finally 블록을 적용하는 작업을 마쳤으면
UserDaoTest 테스트를 수행해보고 이상이 없는지 확인한다.- 이제 서버환경에서도 안정적으로 수행될 수 있으면서 DB 연결 기능을 자유롭게 확장할 수 있는 이상적인 DAO가 완성됐다.
- 하지만! 여전히 뭔가 아쉬움이 남아 있다.
'Java-Spring > 토비의 스프링 3.1' 카테고리의 다른 글
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 템플릿 (3) (0) | 2023.09.28 |
---|---|
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 템플릿 (2) (0) | 2023.09.28 |
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 템플릿 (0) (0) | 2023.09.28 |
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 테스트 (5) (0) | 2023.09.23 |
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 테스트 (4) (0) | 2023.09.23 |