Skip to content

Latest commit

 

History

History
309 lines (192 loc) · 12.4 KB

템플릿.md

File metadata and controls

309 lines (192 loc) · 12.4 KB

들어가며

템플릿이란 이렇게 바뀌는 성질이 다른 코드 중에서 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜서 효과적으로 활용할 수 있도록 하는 기법이다.

3장은 스프링에 적용된 템플릿 기법을 살펴보고, 이를 적용해 완성도 있는 DAO 코드를 만드는 방법을 알아볼 것이다.

3.1 다시 보는 초간남 DAO

UserDao의 코드에는 예외상황에 대한 처리를 하지 않은 문제점이 남아 있다.

3.1.1 에외처리 기능을 갖춘 DAO

JDBC 코드에서 반드시 예외처리를 해야한다.

JDBC 수정 기능의 예외처리 코드

close() 메소드를 통해 리소스를 반환하여 리소스 부족 문제를 예방해야 한다.

그렇기에 가져온 리소스를 반환하도록 try/catch/finally 구분 사용을 권장한다.

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) {
                // 무시
            }
        }
    }
}

3. 2 변하는 것과 변하지 않는 것

3.2.1 JDBC try/catch/finally 코드의 문제점

지금이 완성도 높은 DAO 코드가 되었지만 try/catch/finally 블록이 2중으로 중첩되어 나오기에 답답해 보이는 것이 문제이다.

이럴경우 복사 붙여넣기를 반복하다 실수가 나면 찾기도 힘들고 티가 안나서 못찾다가 문제가 생길 수 있다.

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

    try {
        // 1. 커넥션 획득
        c = dataSource.getConnection();
        
        // 2. 전략을 사용하여 PreparedStatement 생성
        StatementStrategy strategy = new DeleteAllStatement();
        ps = strategy.makePreparedStatement(c);
        
        // 3. 쿼리 실행
        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) {
                // 무시
            }
        }
    }
}

3.2.2 분리와 재사용을 위한 디자인 패턴 적용

가정 먼저 비슷한 기능의 메소드에서 동일하게 나타날 수 있는 변하지 않고 고정되는 부분과, 각 메소드마다 로직에 따라 변하는 부분을 위와 같이 구분을 해야한다.

첫 번째 방법: 메소드 추출

자주 바뀌는 부분을 메소드로 독립을 시켰음에도 이득이 없다.

→ 보통 메소드 추출 리팩토링을 적용하는 경우에는 분리시킨 메소드를 다른 곳에서 재사용 할 수 있어야 한다.

→ 하지만 지금은 분리시키고 남은 메소드가 재사용이 필요한 부분이고 DAO 로직마다 새롭게 만들고 확장해야 하는 것이 문제다.

두 번째 방법: 템플릿 메서드 패턴의 적용

이 방법을 사용해 UserDao 클래스의 기능을 확장하고 싶을 때마다 상속을 통해 자유롭게 확장할 수 있다.

상위 클래스의 불필요한 변화가 안생겨 개발 폐쇄 원칙은 그럭저럭 지킨다.

다만 이 방법을 사용할 수 DAO 로직마다 상속을 통해 새롭게 클래스를 만들어야 한다는 것이 큰 문제이다.

public class UserDaoDeleteAll extends UserDao {
    
    @Override
    protected PreparedStatement makeStatement(Connection c) throws SQLException {
        PreparedStatement ps = c.prepareStatement("delete from users");
        return ps;
    }
}

세 번째 방법: 전략 패턴의 적용

오브젝트를 아예 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 전략패턴을 사용한다.

앞선 방법의 장점은 가져가지만 거기에 유연하고 확장성이 뛰어나다.

하미나 전략 패턴은 필요에 따라 컨텍스트는 그대로 유지되면서 전략을 바꿔 쓸 수 있다는 것인데, 구체적인 전략 클래스를 사용하도록 고정되어 있다면 전략패턴에도 OCP에도 잘 들어맞지는 않다는 문제가 있다.

네 번째 방법: DI 적용을 위한 클라이언트/컨텍스트 분리

클라이언트와 컨텍스트는 클래스를 분리하진 않았지만, 의존관계와 책임으로 볼 때 이상적인 클라이언트/컨텍스트 관계를 갖게 되었습니다.

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 ignored) {} }
        if (c != null) { try { c.close(); } catch (SQLException ignored) {} }
    }
}

마이크로 DI

제3자의 도움을 통해 두 오브젝트 사이의 유연한 관계가 설정되도록 만든다는 것이 가장 중요한 개념이다.

DI는 의존관계에 있는 두 개의 오브젝트와 이 관계를 다이내믹하게 설정해주는 오브젝트 팩토리, 그리고 이를 사용하는 클라이언트라는 4개의 오브젝트 사이에서 일어난다.

하지만 여러가지 다양한 사용법이 있기 때문에 잘 고려해서 활용하면 좋다

3.3 JDBC 전략 패턴의 최적화

3.3.1 전략 클래스의 추가 정보

add() 메서드에 적용해 클래스를 분리하고 나면 컴파일 에러가 나게 된다.

deleleAll()과는 달리 user라는 부가적인 정보가 필요하기 떄문에 생성자를 통해 제공받도록 한다.

이렇게 수정을 하고 나면 deleleAll()과 add두 군데 모두 JDBC컨텍스트를 공유해서 사용할 수 있게 된 것이다.

3.3.2 전략과 클라이언트의 동거

2가지의 개선 사항

  1. DAO 메소드마다 새로운 StatementStrategy 구현 클래스를 만들어야 한다는 것
  2. DAO 메소드에서 StatementStrategy에 전달할 User와 같은 부가적인 정보가 있는 경우 이를 위해 오브젝트를 전달받는 생성자와 이를 저장해둘 인스턴스 변수를 번거롭게 만들어야 한다는 점이다.

로컬 클래스

StatementStrategy 전략 클래스를 매번 독립된 파일로 만들지 말고 UserDao 클래스 안에 내부 클래스로 정의해버리는 방법이 있다.

  1. 코드를 이해하기도 좋아졌다
  2. 로컬 클래스는 클래스가 내부 클래스이기 때문에 자신이 선언된 곳의 정보에 접근할 수 있다는 점이다.

익명 내부 클래스

→ 이름을 갖지 않는 클래스를 말한다. 클래스를 재사용할 필요가 없고 구현한 인터페이스 타입으로만 사용할 경우에 유용하다.

AddStatement를 익명 내부 클래스로 선언한다. 이를 통해 선언과 동시에 오브젝트를 생성하고 인터페이스 타입의 변수에만 저장시킨다.

3. 4 컨텍스트와 DI

3.4.1 JdbcContext의 분리

JdbcContextWithStatementStrategy()를 UserDao 클래스 밖으로 독립시켜서 모든 DAO가 사용할 수 있게 할 것이다.

클래스 분리

DB 커넥션을 필요로 하는 코드는 JdbcContext 안에 있기 때문에 의존관계인 DataSource 타입 빈을 DI 받을 수 있게 해주는 것이 중요하다.

빈 의존관계 변경

스프링의 DI는 기본적으로 인터페이스를 사이에 두고 의존 클래스를 바꿔서 사용하도록 하는 게 목적이다. 하지만 지금은 구현 방법이 바뀔 가능성이 없다.

지금은 빈 의존관계를 따라서 XML 설정파일을 수정하도록 한다.

3.4.2 JdbcContext의 특별한 DI

지금의 DI는 클래스 레벨에서 구체적인 의존관계가 만들어지지 않도록 인터페이스를 사용했다. 하지만 지금은 인터페이스를 거치지 않고 코드에서 바로 클래스를 사용하고 있다.

스프링 빈으로

이것이 문제가 되지는 않을까? 예 문제가 되지는 않습니다.

인터페이스를 사용해서 클래스를 자유롭게 변경할 수 있게 하지는 않았지만 JdbcContext를 UserDao와 DI 구조로 만들어야 할 이유는 무엇일까

  1. JdbcContext가 스프링 컨테이너의 싱글톤 레지스트리에서 관리되는 싱글톤 빈이 되기 때문이다.
  2. JdbcContext가 DI를 통해 다른 빈에 의존하고 있기 떄문이다.

코드를 이용하는 수동 DI

지금의 방안을 해결하기에 가장 좋은 방안은 JdbcContext에 대한 제어권을 갖고 생성과 관리를 담당하는 UserDao에게 DI까지 맡기는 것이 방법이다.

모든 방안에는 방법이 있고 2가지를 다 사용했지만 장단점이 확실히 있었다 하지만 무엇을 선택하든 근거가 있어야 하며 없다면 차라리 인터페이스를 만들어서 평범한 DI구조로 만들도록 하자! - 네!

3.5 템플릿과 콜백

템플릿: 어떤 목적을 위해 미리 만들어둔 모양이 있는 틀을 가리킨다.

콜백: 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트를 말한다.

3.5.1 템플릿/콜백의 동작원리

템플릿/콜백의 특징

일반적인 작업 흐름

  • 클라이언트는 템플릿 안에서 실행될 로직을 담은 콜백 오브젝트를 만들고, 콜백이 참조할 정보를 제공하도록 한다.
  • 템플릿은 정해진 작업 흐름을 따라 작업을 진행하다가 내부에서 생성한 참조정보를 가지고 콜백 오브젝트의 메소드를 호출한다.
  • 템플릿은 콜백이 돌려준 정보를 사용해서 작업을 마저 수행한다.

JdbcContext에 적용된 템플릿/콜백

JdbcContext의 workWithStatementStrategy()템플릿은 리턴 값이 없는 단순한 구조다

3.5.2 편리한 콜백의 재활용

현재의 방식을 개선하기 위한 다른 방법을 사용해볼 것이다.

콜백의 분리와 재활용

바뀌지 않는 부분을 빼내서 메소드로 만들었다. 변하는 것과 변하지 않는 것을 분리하고 변하지 않는 건 유연하게 재활용할 수 있게 만든다는 원리를 적용한 것이다.

콜백과 템플릿의 결합

템플릿의 위치를 메소드를 옮겨 모든 DAO 메소드에서 사용할 수 있게 할 수 있다.

오히려 응집력이 강한 코드를 한 군데 모아두는 것이 유리하다.

3.5.3 템플릿/콜백의 응용

테스트와 try/catch/finally

만들어진 모든 리소스를 확실히 정리하고 빠져나오도록 만드는 것

모든 예외 상황에 대해서는 적절한 처리를 해주도록 하는 것

중복의 제거와 템플릿/콜백 설계

템플릿/콜백의 재설계

제네릭스를 이용한 콜백 인터페이스

3.6 스프링의 JdbcTemplate

스프링이 제공하는 템플릿/콜백 기술을 설명할 예정이다.

앞부분은 사실상 그냥 개선의 이야기로 보고 이해하는 것이 중요하다.

3.6.5 재사용 가능한 콜백의 분리

현재는 많이 개선이 되었지만 아직도 할 일이 있다.

DI를 위한 코드 정리

필요없어진 인스턴스 변수 제거

단 두 번 나온 메소드라도 앞으로 추가될 기능을 고려해 변경할 여지가 있다면 중복을 없게 해야한다.

템플릿/콜백 패턴과 UserDao

3장이 끝날 무렵에 완성된 코드는 거의 완벽하지만 개선사항이 남아있다.

  1. userMapper가 인스턴스 변수로 설정되어 있고, 한 번 만들어지면 변경되지 않ㅇ는 프로퍼티와 같은 성격을 띠고 있으니 따로 분리하는 것
  2. DAO 메소드에서 사용하는 SQL문장을 외부 리소스에 담고 읽어와 사용하게 하는 것