<토비의스프링> 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; } }
문제점
- 확장시 클래스를 계속해서 만들어줘야함
- 클레스 레벨에서 컴파일 시점에 이미 그관계가 결정됨
전략 패턴 적용
오브젝트를 아예 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하게함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 구현클래스를 만들어야함
- 부가적인 정보가 있을경우 생성자와 인스턴스 변수도 만들어야함
로컬클래스
로컬변수 선언하듯 클래스도 선언
로컬클래스는 선언된 메서드 내에서만 사용가능
장점
- 클래스파일이 하나 줄어듬
- 자신이 선언된 곳의 정보에 접근할 수 있음
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하는데 문제없나!?
- jdbcContext가 싱글톤 레지스트리에서 관리되는 Bean
- DI를 통해 다른 빈에 의존하고있음
- 주입되는 오브젝트와 주입받는 오브젝트 양쪽모두 스프링빈으로 등록
3.5 템플릿과 콜백
탬플릿 콜백 패턴
- 전략패턴의 기본구조에 익명 내부클래스를 활용
- 컨텍스트 : 템플릿, 익명클래스로 만들어진 오브젝트 : 콜백
- 템플릿
- 고정된 틀의 로직을 가진 템플릿메소드를 슈퍼클래스에 두고 바뀌는 것을 서브클래스의 메소드에 두는 구조
- 콜백
- 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트
- 템플릿
- 동작원리
- 단일 메소드 인터페이스를 사용
- 콜백은 하나의 메소드를 가진 인터페이스를 구현한 익명 내부클래스로 만들어짐
- 메소드의 파라미터는 컨텍스트 정보를 전달받을때 사용
- 단일 메소드 인터페이스를 사용
- 예시
- add → StatementStrategy(익명내부클래스형태로 메서드 던짐) → jdbcContext 실행
예시에서 문제점
- DAO에서 익명클래스를 사용하여 지저분함
- 해결방법 : 콜백의 분리와 재활용
- StatementStrategy의 SQL문 이외에는 변하지 않음
- 별도의 메소드로 분리 (executeSql) → JdbcContext로 이동
- StatementStrategy의 SQL문 이외에는 변하지 않음
템플릿/콜백 응용
숫자계산기 예제(txt파일 입출력)
더하기
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()); } } } }
곱해야하는 기능이 추가됨 → 템플릿/콜백 적용
구조
- 템플릿이 파일을 열고 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) }
- 구조가 비슷할경우
더하기와 곱하기가 크게 차이가 없다
- 읽는 부분을 공통으로 빼자
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); }
제네릭을 이용
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; } }// 결과를 하나의 오브젝트에 매핑 ); }
반응형
'개발서적 > 토비의스프링' 카테고리의 다른 글
<토비의스프링> 6.4~6.5 AOP(2) (0) | 2021.02.20 |
---|---|
<토비의스프링> 6.1~6.3 AOP (0) | 2021.02.18 |
<토비의스프링> 5.1~5.2 서비스추상화 (0) | 2021.02.17 |
<토비의스프링> 4.1~4.2 예외 (0) | 2021.02.17 |
<토비의스프링> 1.7~1.8 DI (0) | 2021.02.15 |
블로그의 정보
57개월 BackEnd
BFine