반응형
예외 처리 코드를 통해서 템플릿이 필요하게 된 계기(반복 제거)
예외 처리가 있는 DAO 객체
DB Connection
- DB는 connection이라는 제한적인 리소스를 공유해서 사용
- 중요한 요소 : 예외처리
- 예외가 발생 -> 바로 자원 반납하도록 만들어야 한다.
public void deleteAll() throws SQLException {
Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement("delete from users");
ps.executeUpdate();
ps.close();
c.close();
}
- Connection 을 사용해서 connection pool을 가져온다.
- 정상속으로 끝나면 close() 실행 -> 자원 반납
- 예외 : 중간에 예외가 발생하면 실행을 끝마치지 못 하면 자원 반납하지 않고 종료하는 문제 발생
- 예외가 반복적으로 발생 -> 커넥션 풀이 부족 -> 애플리케이션 전체가 종료될 수 있다.
- JDBC에서는
try-catch-finally
를 사용하도록 권장(예외 처리) - 예외 추가한 코드
public void deleteAll() throws SQLException {
Connection c = null;
PreparedStatement ps = null;
try {
// Connection 발생
c = dataSource.getConnection();
ps = c.prepareStatement("delete from users");
} catch (SQLException e) {
throw e;
} finally {
if (ps != null) {
try {
ps.clearParameters();
} catch (SQLException e) {
}
}
if (c != null) {
try {
c.close();
} catch (SQLException e) {
}
}
}
}
- 예외 발생할 수 있는 코드를 모두 try-catch로 감싼다.
- finally를 붙여서 예외발생 유무에 관계없이 자원 반납하는 코드 실행
- 추가 예외 사항
- getConnection() 실패시 close하면 예외 발생
- ps 생성하다 예외 발생시 connection close() 가능 하지만 ps close() 불가
- 추가 try catch 문으로 구조 생성 필요
- 추가 예외를 추가한 경우
public int getCount() throws SQLException {
Connection c = null;
PreparedStatement ps = null;
ResultSet rs = null;
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) {
}
}
}
}
- try catch를 중첩으로 모든 메서드에 넣어줄 수 없다
- 이유 : 많은 노동일 필요하고 유지 보수도 힘들다
유지 보수에 편리한 문제 해결 방법
템플릿 메소드 적용
- 상속을 통해서 기능 확장을 하여 사용하는 방식으로서 변하지 않는 슈퍼클래스를 두고, 변하는 자식 클래스를 재정의해서 사용하는 디자인 패턴이다.
- 변하지 않는 슈퍼 클래스
abstract protected PreparedStatement makeStatement(Connection c) throws SQLException;
- 단점 : DAO 메소드 로직 마다 하나의 상속 객체가 생성 되어야하는 한계가 발생
전략 패턴 적용하기
- 개방 패쇄 원칙(OCP)를 잘 지키며 템플릿 패턴보다 더 유연한 방법
public interface StatementStrategy {
PreparedStatement makePreaparedStatement(Connection c) throws SQLException;
}
public class DeleteAllStatement implements StatementStrategy {
public PreparedStatement makePreaparedStatement(Connection c) throws SQLException {
PreparedStatement ps = c.prepareStatement("delete from users");
return ps;
}
}
// UserDao method
public void deleteAll() throws SQLException {
...
try {
c = dataSource.getConnection();
StatementStrategy strategy = new DeleteAllStatement();
ps = strategy.makePreaparedStatement(c);
ps.executeUpdate();
} ....
}
- PreparedStatement를 외부에서 주입을 받아서 사용
- 문제점
- StatementStrategy라는 객체 또한 계속 해서 만들어야 하는 문제 발생
- StatementStrategy에 User와 같은 DTO 객체나 Entity 객체를 전달할 수 없는 한계가 발생
전략 패턴 문제 해결 방식인 로컬 클래스와 익명 클래스
로컬 클래스
- 로컬 클래스: 클래스 내부에 생성하는 클래스로서 현재 외에있는 클래스의 변수에 접근이 가능 -> User와 같은 Entity 객체에도 접근 가능
- strategy와 같은 클래스를 계속해서 상속해서 java 파일에 만들어 줄 필요 없음
익명 클래스
- 익명 클래스 : 이름을 제거하여 한번 사용을 목적으로 만드는 클래스
public class JdbcContext { // 변경되지 않는
context private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void workWithStatementStrategy(StatementStrategy stmt) throws SQLException {
Connection c = null;
PreparedStatement ps = null;
try {
c = this.dataSource.getConnection();
ps = stmt.makePreaparedStatement(c);
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) {}
}
}
}
}`
//DAO Class Method
public void add(final User user) throws SQLException {
this.jdbcContext.workWithStatementStrategy(new StatementStrategy() { // 익명 내부 클래스
public PreparedStatement makePreaparedStatement(Connection c) throws SQLException {
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());
return ps;
}
});
}
- 위의 코드에서 add 함수 내부에 임시로 사용할 클래스 생성
- 위와 같은 방식을 제공하고 있는 것이 Spring 에서는 JdbcTemplate이다.
JdbcTemplate
- 정의 : JDBC의 try catch문으로 예외 발생시 connection pool을 반납하는 중복 코드를 template로 만들어서 재사용과 확장에 용의하게 만드는 클래스
public int insert(Book book) throws Exception {
return this.jdbcTemplate.update(
"insert into teamproject.book(isbn, title,author,publisher,is_borrow,customer_id) values(?,?,?,?,?,?)",
new Object[]{book.getIsbn(), book.getTitle(), book.getAuthor(), book.getPublisher(), book.getIsBorrow(), book.getCustomerId()}
);
}
public Optional<Book> searchByIsbn(String isbn) throws Exception{
return this.jdbcTemplate.queryForObject(
"select * from teamproject.book where isbn=?",
new Object[]{isbn},
(rs, rowNum) ->
Optional.of(Book.builder()
.isbn(rs.getString("isbn"))
.title(rs.getString("title"))
.author(rs.getString("author"))
.publisher(rs.getString("publisher"))
.isBorrow(rs.getInt("is_borrow"))
.customerId(rs.getString("customer_id"))
.build())
);
}
- 위 예시와 동일하게 stmt와 stmt에 적용할 value 값을 외부에서 주입한다.
- 반환타입에 맞게 mapping 하도록 callback함수/람다식을 사용해서 주입할 수 있도록 만드는 것이다.
반응형
'Spring > 개념' 카테고리의 다른 글
Spring Test Mock 사용법 및 특징 (1) | 2021.10.30 |
---|---|
Spring Boot로 MySQL replication 연동하기 (0) | 2021.10.06 |
04 Exception (0) | 2021.05.19 |
01 장 오브젝트와 의존관계 (0) | 2021.05.05 |
02장 Test (0) | 2021.04.20 |