본문 바로가기
Spring/개념

03 템플릿

by clearinging 2021. 5. 8.
반응형

예외 처리 코드를 통해서 템플릿이 필요하게 된 계기(반복 제거)

예외 처리가 있는 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 메소드 로직 마다 하나의 상속 객체가 생성 되어야하는 한계가 발생

template method 패턴 적용 한 모습

전략 패턴 적용하기

  • 개방 패쇄 원칙(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