과정을 즐기자

템플릿 본문

Spring

템플릿

320Hwany 2023. 4. 2. 11:53

이 글은 토비의 스프링을 읽고 작성한 글입니다.

DB 커넥션이라는 제한적인 리소스를 공유해 사용하는 서버에서 동작하는 JDBC 코드에는
반드시 지켜야 할 원칙이 있습니다. 바로 예외처리입니다.
예외가 발생했을 경우에도 사용한 리소스를 반드시 반환해야 합니다.
이때 어디서 에러가 발생하느냐에 따라 반환해야 하는 리소스가 다릅니다.
deleteAll() 메소드를 살펴보겠습니다.

deleteAll()

 public void deleteAll() throws SQLException {
        Connection c = null;
        PreparedStatement ps = null;

        try {
            c = dataSource.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) {
                }
            }
        }
    }

위 코드는 예외처리를 잘 처리하였지만 다른 메소드를 만들 때 이와 중복되는 코드가 많습니다.
이렇게 많은 곳에서 중복되는 코드와 로직에 따라 자꾸 확장되고 자주 변하는 코드를 분리할 필요가 있습니다.
이제부터 이 코드를 개선하는 작업을 해보겠습니다.

메소드 추출

변하는 부분을 메소드로 빼는 것을 생각해 볼 수 있습니다. 이러한 메소드 추출을 하는 경우에는
추출한 부분을 재사용할 수 있어야 합니다. 하지만 여기서는 재사용하는 부분이 추출한 부분이 아니라
남은 메소드이므로 별로 좋은 방법은 아닌 것 같습니다.

템플릿 메소드 패턴의 적용

변하지 않는 부분은 슈퍼클래스에 두고 변하는 부분은 서브 클래스에서 추상 메소드를 오버라이드하여
새롭게 정의하는 방법이 있습니다. 하지만 이 방법의 문제는 DAO 로직 마다 상속을 통해 새로운
클래스를 만들어야 한다는 것입니다. 또 확장구조가 이미 클래스를 설계하는 시점에서 고정되어
상속을 통해 확장을 하는 템플릿 메소드 패턴의 단점이 드러납니다.

전략 패턴의 적용

오브젝트를 아예 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 전략 패턴을
이용하는 방법입니다. 변하지 않는 컨텍스트로 만들고 이 컨텍스트는 전략 인터페이스 의존하는 것입니다.
컨텍스트가 인터페이스만 알고 특정 구현 클래스를 모르고 있으므로 OCP 잘 들어맞는다고 할 수 있습니다.

public void deleteAll() throws SQLException {
    StatementStrategy st = new DeleteAllStatement();
    jdbcContextWithStatementStrategy(st);
}
public class DeleteAllStatement implements StatementStrategy {

    @Override
    public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
        PreparedStatement ps;
        ps = c.prepareStatement("delete from users");
        return ps;
    }
}
public void jdbcContextWithStatementStrategy(StatementStrategy stmt) throws SQLException {
        Connection c = null;
        PreparedStatement ps = null;

        try {
            c = dataSource.getConnection();

            ps = stmt.makePreparedStatement(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) {
                }
            }
        }
    }

전략 패턴의 최적화

위 방법도 DeleteAllStatement 클래스를 생성해야 합니다. 좀 더 개선하기 위해서는
익명 내부 클래스를 사용해서 매번 전략을 새로 만들어서 사용할 수 있습니다.

public void deleteAll() throws SQLException {
    jdbcContextWithStatementStrategy(
            new StatementStrategy() {
                public PreparedStatement makePreparedStatement(Connection c) 
                            throws SQLException {
                    PreparedStatement ps;
                    ps = c.prepareStatement("delete from users");
                    return ps;
                }
            }
    );
}

템플릿 콜백 패턴

이렇게 전략 패턴의 기본 구조에 익명 내부 클래스를 활용한 방식을 스프링에서는 템플릿 콜백 패턴이라고 부릅니다.
전략 패턴의 컨텍스트를 템플릿이라고 부르고, 익명 내부 클래스로 만들어지는 오브젝트를 콜백이라고 부릅니다.
템플릿 콜백 패턴의 콜백은 보통 단일 메소드 인터페이스를 사용합니다. 콜백 인터페이스의 메소드에는
보통 파라미터가 있고 이 파리미터는 템플릿의 작업 흐름 중에 만들어지는 컨텍스트 정보를 전달받을 때 사용합니다.

여기서 또 실제로 바뀌는 부분은 쿼리를 받는 부분이므로 템플릿 콜백 패턴을 적용하면 deleteAll()메소드를
더욱 간결하게 표현할 수 있습니다.

public void deleteAll() throws SQLException {
    jdbcContext.executeSql("delete from users");
}
public void executeSql(final String query) throws SQLException {
    workWithStatementStrategy(
            new StatementStrategy() {    
                public PreparedStatement makePreparedStatement(Connection c) 
                        throws SQLException {
                    PreparedStatement ps;
                    ps = c.prepareStatement(query);
                    return ps;
                } 
            }
    );
}

xxxTemplate 형태

스프링에서는 여러 템플릿 콜백 기술을 제공합니다. xxxTemaplate 형태를 보인다면 템플릿 콜백 패턴이
적용되었다는 것을 알 수 있습니다. 대표적으로 JdbcTemplate이 있습니다.
JdbcTemplate은 Jdbc를 이용하는 DAO에서 사용할 수 있도록 준비된 다양한 템플릿과 콜백을 제공합니다.

정리

-일정한 작업 흐름이 반복되면서 그중 일부 기능만 바뀌는 코드가 존재한다면 바뀌지 않는 부분은 컨텍스트로
바뀌는 부분은 전략으로 만드는 전략 패턴을 적용할 수 있습니다.
-단일 전략 메소드를 갖는 전략 패턴이면서 익명 내부 클래스를 사용해서 매번 전략을 새로 만들어 사용하고
컨텍스트 호출과 동시에 전략 DI를 수행하는 방식을 템플릿 콜백 패턴이라고 합니다.

출처 : 토비의 스프링 3.1 Vol.1 스프링의 이해와 원리

'Spring' 카테고리의 다른 글

서비스 추상화  (0) 2023.04.11
예외  (0) 2023.04.03
테스트  (0) 2023.03.26
오브젝트와 의존관계  (0) 2023.03.25
요청과 응답에서 엔티티를 직접 사용하면 안되는 이유  (0) 2023.02.23