You will be fine

<토비의스프링> 3.1~3.5 템플릿

by BFine
반응형

3-1 다시보는 DAO

  • 예외사항에 대한 처리가 부족
PreparedStatement ps = c.prepareStatement("selet * from");
ps.executeUpdate(); // 요기서 예외발생시 중단

ps.close(); // 리소스를 반환한다.
c.close();
  • 오류가 날때마다 미처 반환되지 못한 Connection이 쌓이면 커넥션 풀에 여유가 없어짐
finally{
    if(ps !=null){
        try{ps.close();}catch(SQLExcetpion e){}
    }
}
.....
  • null 상태의 변수의 close 호출시 NullPointException이 발생하기 때문에 확인필요

3-2 문제점

  • 복잡한 try catch 구문이 2중으로 중첩되고 메소드마다 반복

  • 변하는 부분과 변하지 않는 부분을 나눈다.

  • 템플릿 메서드 적용

    • 상속을 통해 기능을 확장해서 사용함

    • 변하지않는 부분은 슈퍼클래스에 두고 변하는 부분을 추상메서드로 정의해서 서브 클래서에서 오러바이드하여 새롭게 사용

      public class UserDaoDeleteAll extends UserDao{
        PreparedStatement makeStatement(...){
                PreparedStatement ps = c.prepareStatement(...);
                return ps;
        }
      }
    • 문제점

      1. 확장시 클래스를 계속해서 만들어줘야함
      2. 클레스 레벨에서 컴파일 시점에 이미 그관계가 결정됨
  • 전략 패턴 적용

    • 오브젝트를 아예 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하게함p

      public interface StatementStrategy{
      PreparedStatement makeStatement(...) throws SQLException;
      }
      
      public class UserDaoDeleteAll extends StatementStrategy{
        PreparedStatement makeStatement(...){
                PreparedStatement ps = c.prepareStatement(...);
                return ps;
        }
      }
    • 적용

      StatementStrategy st = new UserDaoDeleteAll();
      PreparedStatement ps = st.makeStatement(...);
      ps.executeUpdate(); // 요기서 예외발생시 중단
      
      ps.close(); // 리소스를 반환한다.
      c.close();
    • 문제점

      • 이미 주체적인 전략클래스가 고정되어있음
    • 분리

      • Context(UserDao)가 어떤전략을 사용할건지 Client가 결정
      • Client가 StatementStrategy를 파라미터로 넘겨준다.

3-3 최적화

  • 개선 필요사항

    • DAO 메소드마다 새로운 StatementStrategy 구현클래스를 만들어야함
    • 부가적인 정보가 있을경우 생성자와 인스턴스 변수도 만들어야함
  • 로컬클래스

    • 로컬변수 선언하듯 클래스도 선언

    • 로컬클래스는 선언된 메서드 내에서만 사용가능

    • 장점

      1. 클래스파일이 하나 줄어듬
      2. 자신이 선언된 곳의 정보에 접근할 수 있음
      public void add(final User user) throws SQLException{
        class AddStatement implements StatementStrategy{
            ...
         return ps;
      }
      }
      StatementStrategy st = new AddStatement(); // 전달할 필요 없어짐
      }
  • 익명클래스

      StatementStrategy st = new StatementStrategy(){
         ...
      };

3.4 컨텍스트와 DI

  • 전략패턴의 구조로 본 DAO
    • UserDao의 메서드 : 클라이언트
    • 익명클래스 : 개별적인 전략
    • jdbcContextWithStatementStrategy : 컨텍스트
  • 모든 Dao에서 사용할수 있도록 Dao의 jdbcContextWithStatementStrategy 를 클래스화
  • 의존관계의 변화
    • UserDao -→ JdbcContext -→ DataSource ← SimpleDriverDB
  • DI
    • 인터페이스를 두어 클래스에서 의존관계를 고정안하고 런타임시에 오브젝트 관계를 동적주입
  • jdbcContext위에는 클래스레벨에서 DI하는데 문제없나!?
    1. jdbcContext가 싱글톤 레지스트리에서 관리되는 Bean
    2. DI를 통해 다른 빈에 의존하고있음
      • 주입되는 오브젝트와 주입받는 오브젝트 양쪽모두 스프링빈으로 등록

3.5 템플릿과 콜백

  • 탬플릿 콜백 패턴

    • 전략패턴의 기본구조에 익명 내부클래스를 활용
    • 컨텍스트 : 템플릿, 익명클래스로 만들어진 오브젝트 : 콜백
      1. 템플릿
        • 고정된 틀의 로직을 가진 템플릿메소드를 슈퍼클래스에 두고 바뀌는 것을 서브클래스의 메소드에 두는 구조
      2. 콜백
        • 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트
    • 동작원리
      • 단일 메소드 인터페이스를 사용
        • 콜백은 하나의 메소드를 가진 인터페이스를 구현한 익명 내부클래스로 만들어짐
        • 메소드의 파라미터는 컨텍스트 정보를 전달받을때 사용
    • 예시
      • add → StatementStrategy(익명내부클래스형태로 메서드 던짐) → jdbcContext 실행
  • 예시에서 문제점

    • DAO에서 익명클래스를 사용하여 지저분함
    • 해결방법 : 콜백의 분리와 재활용
      • StatementStrategy의 SQL문 이외에는 변하지 않음
        • 별도의 메소드로 분리 (executeSql) → JdbcContext로 이동
  • 템플릿/콜백 응용

    • 숫자계산기 예제(txt파일 입출력)

      1. 더하기

        public Integer calcSum(String filepath) throws IOException {
             BufferedReader br = null;
             try {
                 br = new BufferedReader(new FileReader(filepath));
                 int sum = 0;
                 String line = null;
                 while((line = br.readLine()) != null) {
                     sum += Integer.valueOf(line); 
                 }
                 return sum;
        
        }catch(IOException e) {
                 System.out.println(e.getMessage());
                 throw e;
        
        }finally {
                 if (br != null) {
                     try { br.close(); } 
                     catch(IOException e) { System.out.println(e.getMessage()); }
                 }
             }
         }
      2. 곱해야하는 기능이 추가됨 → 템플릿/콜백 적용

      • 구조

        • 템플릿이 파일을 열고 Reader를 만들어 콜백에게 전달
        • 콜백이 라인을 읽어서 처리 후 결과만 템플릿으로 전달
        public interface BuffedReaderCallback{
              Integer doSomethingWithReader(BufferedReader br)
        }
        
        public Integer readTemplate(String filepath, BuffedReaderCallback callback ) throws IOException {
              BufferedReader br = null;
              try {
                  br = new BufferedReader(new FileReader(filepath));
                  int ret = callback.doSomethingWithReader(br);
                  return sum;
        
         }catch(IOException e) {
                  System.out.println(e.getMessage());
                  throw e;
        
         }finally {
                  if (br != null) {
                      try { br.close(); } 
                      catch(IOException e) { System.out.println(e.getMessage()); }
                  }
              }
          }
        
        public Integer calcSum(String filepath) throws IOException {
              BuffedReaderCallback sumCallback = 
                      new BuffedReaderCallback (){
                                  public doSomethingWithReader(BufferedReader br){
                                          int sum = 0;
                                          String line = null;
                                          while((line = br.readLine()) != null) {
                                              sum += Integer.valueOf(line); // 요부분만 변경하면됨
                                          }
                                          return sum;
                                  }
                          };
              return readTemplate(filepath, sumCallbcak)
        } 
      1. 구조가 비슷할경우
      • 더하기와 곱하기가 크게 차이가 없다

        • 읽는 부분을 공통으로 빼자
        public interface LineCallback {
          Integer doSomethingWithLine(String line,Integer value);
        }
        
        public Integer lineReadTemplate(String path, LineCallback callback, int initVal) throws IOException {
              BufferedReader br = null;
              try {
                              // 공통 추출
                  br = new BufferedReader(new FileReader(path));
                  Integer res = initVal;
                  String line = null;
                  while ((line = br.readLine()) != null) {
                      res = callback.doSomethingWithLine(line, res);
                  }
                  return res;
        
               } catch (IOException e) {...} 
                          finally {...}
          }
        }
        
        public Integer calcSum(String filepath) throws IOException {
           LineCallback sumCallback = 
                      new LineCallback() {
                  public Integer doSomethingWithLine(String line, Integer value) {
                      return value + Integer.valueOf(line);
                  }
              };
              return lineReadTemplate(filepath, sumCallback, 0);
          }
        
        public Integer calcMultiple(String filepath) throws IOException {
          LineCallback multipleCallback = 
                  new LineCallback() {
              public Integer doSomethingWithLine(String line, Integer value) {
                  return value * Integer.valueOf(line);
              }
          };
          return lineReadTemplate(filepath, multipleCallback, 1);
        }
        
      1. 제네릭을 이용

        public interface LineCallback<T> {
         T doSomethingWithLine(String line, T value);
        }
        
        public <T> T lineReadTemplate(String filepath, LineCallback<T> callback, T initVal) throws IOException {
             BufferedReader br = null;
             try {
                 br = new BufferedReader(new FileReader(filepath));
                 T res = initVal;
                 String line = null;
                 while((line = br.readLine()) != null) {
                     res = callback.doSomethingWithLine(line, res);
                 }
                 return res;
             }
             catch(IOException e) {
                 System.out.println(e.getMessage());
                 throw e;
             }
             finally {
                 if (br != null) {
                     try { br.close(); } 
                     catch(IOException e) { System.out.println(e.getMessage()); }
                 }
             }
         }
        public Integer calcSum(String filepath) throws IOException {
             LineCallback<Integer> sumCallback = 
                 new LineCallback<Integer>() {
                     public Integer doSomethingWithLine(String line, Integer value) {
                         return value + Integer.valueOf(line);
                     }};
             return lineReadTemplate(filepath, sumCallback, 0);
         }
        
         public Integer calcMultiply(String filepath) throws IOException {
             LineCallback<Integer> multiplyCallback = 
                 new LineCallback<Integer>() { 
                     public Integer doSomethingWithLine(String line, Integer value) {
                         return value * Integer.valueOf(line);
                     }};
             return lineReadTemplate(filepath, multiplyCallback, 1);
         }
    • JdbcTemplate

      • 스프링이 제공하는 템플릿/콜백

      • queryForInt()

        public int getCount() throws SQLException{
          return this.jdbcTemplate.query(
           new PreparedStatementCreator() {
              public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                  return con.prepareStatement("select count(*)");
              }
           }, // connection 받고 prepareStatement 돌려준다.
        
           new ResultSetExtractor<Integer>() {
              public Integer extractData(ResultSet rs) throws SQLException, DataAccessException {
                  rs.next();
                  return rs.getInt(1);
              }
          }); // 템플릿한테 ResultSet 받아서 결과를 반환한다.
        }
        
        public int getCount() {
              return this.jdbcTemplate.queryForInt("select count(*) from users");
      • RowMapper

        public User get(String id) throws SQLException {
          return this.jdbcTemplate.queryForObject("... where id = ?", 
                      new Object[] {id},// 뒤에 파라미터가 있어서 배열을 사용
              new RowMapper<User>() {
                  public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                      User user = new User();
                      user.setId(rs.getString("id"));
                      user.setName(rs.getString("name"));
                      user.setPassword(rs.getString("password"));
                      return user;
                  }
              }// 결과를 하나의 오브젝트에 매핑
          );
        }
반응형

블로그의 정보

57개월 BackEnd

BFine

활동하기