Chapter 03 본 내용은 토비의 스프링 3 책의 내용을 정리한 것입니다.
토비의 스프링은 스프링 뿐아니라 객체 지향의 기본 원리, 테스팅, 예외 처리, 디자인 패턴 등 Java 개발자라면 반드시 알아야 하는 내용을 스토리 전개 방식으로 점진적으로 친절하게 설명해주는 명저입니다. 
똑같은 내용으로 미국에서 영어로 출간되었다면 Jolt상을 받기에도 충분한 책이라고 생각합니다.
책의 절대적인 가격은 높아보이지만 웬만한 책 두어권 보는 것보다 이 한 권의 책을 여러번 보는 것이 비용 대비 효과가 훨씬 좋을 것입니다.
http://www.acornpub.co.kr/book/toby-spring3-1-set 
DB 관련 자원 반납 DAO에서 DB Connection을 가져오는 부분을 DataSource 인터페이스로 분리하고, DataSource 구현체를 DI 받아오도록 개선했지만, JDBC 리소스의 반납 관련 예외 처리 코드가 여전히 DAO에 남아있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public  class  UserDao      private  DataSource dataSource;     public  void  setDataSource (DataSource dataSource)           this .dataSource = dataSource;     }     private  Connection getConnection ()           dataSource.getConnection();     }     public  void  deleteAll ()  throws  SQLException          Connection c = null ;         PreparedStatement ps = null ;         try  {             c = getConnection();             ps = c.prepareStatement("delete from 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) {} }         }     }     public  ResultSet getAll ()  throws  SQLException          Connection c = null ;         PreparedStatement ps = null ;         ResultSet rs = null ;         try  {             c = getConnection();             ps = c.prepareStatement("select from users" );             rs = ps.executeQuery();             return  rs;         } 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) {} }         }     }     } 
자원 반납 코드의 중복 위와 같이 DAO 메서드마다 자원 반납 코드가 계속 중복된다.
그리고 DAO 메서드에는 다음과 같은 공통 흐름이 있다.
dataSource에서 DB연결을 가져오고 
쿼리 생성 
쿼리 실행 
예외 발생 시 던지기 
모든 처리 후 자원 반납 처리 
 
여기에서 변하는 부분(전략)은 쿼리 생성, 나머지는 변하지 않는다(컨텍스트).
전략 패턴을 통한 전략 분리 변하지 않는 부분을 하나의 메서드(여기에서는 jdbcContextWithStatementStrategy())로 빼내고, 변하는 부분을 jdbcContextWithStatementStrategy()에 인자로 주입하는 방식으로 컨텍스트(자원 반납 처리 부분)와 전략(쿼리 생성 부분)을 분리한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public  class  UserDao      private  DataSource dataSource;     public  void  setDataSource (DataSource dataSource)           this .dataSource = dataSource;     }     private  Connection getConnection ()           dataSource.getConnection();     }     public  deleteAll ()  throws  SQLException                                     StatementStrategy strategy = new  DeleteAllStatement();         jdbcContextWithStatementStrategy(strategy);     }     public  add ()  throws  SQLException                                     StatementStrategy strategy = new  SelectAllStatement();         jdbcContextWithStatementStrategy(strategy);        }     public  void  jdbcContextWithStatementStrategy (StatementStrategy strategy)  throws  SQLException                   Connection c = null ;         PreparedStatement ps = null ;         try  {             c = getConnection();                          ps = strategy.makeStatement(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) {} }         }         } } public  interface  StatementStrategy      PreparedStatement makeStatement (Connection c)  throws  SQLException ; } public  class  DeleteAllStatement  implements  StatementStrategy           public  PreparedStatement makeStatement (Connection c)  throws  SQLException          return  c.prepareStatement("delete from users" );     } } public  class  SelectAllStatement  implements  StatementStrategy           public  PreparedStatement makeStatement (Connection c)  throws  SQLException          return  c.prepareStatement("select * from users" );     } } 
조금 더 최적화해서 전략을 익명 클래스로 바꿔보자
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public  void  deleteAll ()  throws  SQLException     jdbcContextWithStatementStrategy(         new  StatementStrategy() {             public  PreparedStatement makeStatement (Connection c)              throws  SQLException  {                return  c.prepareStatement("delete from users" );             }         }     } } public  void  add (final  User user)  throws  SQLException     jdbcContextWithStatementStrategy(         new  StatementStrategy() {             public  PreparedStatement makeStatement (Connection c)              throws  SQLException  {                PreparedStatement ps =                      c.prepareStatement("inser into users(id, name, password) values (?, ?, ?)" );                                  ps.setString(1 , user.getId());                 ps.setString(2 , user.getName());                 ps.setString(3 , user.getPassword());                 return  ps;             }         }     } } 
메서드 였던 jdbcContextWithStatementStrategy를 JdbcContext 클래스로 분리 DB 연결을 가져오고 DB 연결 자원을 반납하는 jdbcContextWithStatementStrategy()는 UserDao 뿐 아니라 다른 DAO에서도 사용될 수 있다.
재사용할 수 있도록 별도의 클래스로 분리하자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public  class  JdbcContext      DataSource dataSource;     public  void  setDataSource (DataSource dataSource)           this .dataSource = dataSource;     }     private  Connection getConnection ()           dataSource.getConnection();     }     public  void  processStatement (StatementStrategy strategy)  throws  SQLException          Connection c = null ;         PreparedStatement ps = null ;         try  {             c = getConnection();                          ps = strategy.makeStatement(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) {} }         }       } } public  class  UserDao      JdbcContext context;     public  void  setJdbcContext (JdbcContext context)           this .context = context;     }     public  void  deleteAll ()  throws  SQLException                            this .context.processStatement(             new  StatementStrategy() {                 public  PreparedStatement makeStatement (Connection c)                  throws  SQLException  {                    return  c.prepareStatement("delete from users" );                 }             }         );     }     public  void  add (final  User user)  throws  SQLException                            this .context.processStatement(             new  StatementStrategy() {                 public  PreparedStatement makeStatement (Connection c)                  throws  SQLException  {                    PreparedStatement ps =                          c.prepareStatement("inser into users(id, name, password) values (?, ?, ?)" );                                          ps.setString(1 , user.getId());                     ps.setString(2 , user.getName());                     ps.setString(3 , user.getPassword());                     return  ps;                 }             }         );     } } 
중복은 아직도 남아있다. 바로, 익명 클래스 생성 부분이다. 메서드로 빼서 최적화하자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public  class  JdbcContext      DataSource dataSource;     public  void  setDataSource (DataSource dataSource)           this .dataSource = dataSource;     }     private  Connection getConnection ()           dataSource.getConnection();     }     public  void  executeSql (final  String query)                             processStatement(             new  StatementStrategy() {                 public  PreparedStatement makeStatement (Connection c)  throws  SQLException                      return  c.prepareStatement(query);                 }             )         )     }                    private  void  processStatement (StatementStrategy strategy)  throws  SQLException          Connection c = null ;         PreparedStatement ps = null ;         try  {             c = getConnection();                          ps = strategy.makeStatement(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) {} }         }       } } public  class  UserDao      JdbcContext context;     public  void  setJdbcContext (JdbcContext context)           this .context = context;     }     public  void  deleteAll ()  throws  SQLException                   this .context.executeSql("delete from users" );     } } 
클래스 다이어그램 지금까지 개선한 내용을 클래스 다이어그램으로 표시하면 다음과 같다.
Spring의 JdbcTemplate Spring에서는 JdbcTemplate을 제공해서 앞에서 알아본 JdbcContext가 하는 역할을 편리하게 수행할 수 있게 해준다.
Spring JdbcTemplate은 update(), queryForInt(), queryForObject(), query() 등의 메서드를 지원하며, 실전에서는 JdbcTemplate를 확장한 NamedParameterJdbcTemplate을 주로 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 public  class  UserDao      JdbcTemplate jdbcTemplate;     public  void  setJdbcTempate (JdbcTemplate template)           this .template = template;     }     public  void  deleteAll ()  throws  SQLException                   this .template.update("delete from users" );     } } 
사실상 쿼리문만 명시해주면 DB 연결과 DB 연결 자원 반납은 더 이상 신경쓰지 않게 되었다. 초난감 DAO와 비교해보면 정말 많은 부분이 개선되었다.
하지만 곳곳에 있는 throws SQLException이 눈에 거슬린다.
4장에서는 예외를 처리하는 방법에 대해 알아본다.