본문 바로가기

Spring/토비의 스프링 3.1

3.3 JDBC 전략 패턴의 최적화


3.3.1 전략 클래스의 추가 정보

add() 메소드에 전략 패턴 적용

  • add()에서는 PreparedStatement를 만들 때 user라는 부가적인 정보가 필요
  • user를 제공해주어야 함
  • AddStatement의 생성자를 통해 제공받도록

UserDao.java

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

AddAllStatement.java

public class AddAllStatement implements StatementStrategy {
    User user;

    public AddAllStatement(User user) {
        this.user = user;
    }

    @Override
    public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
        PreparedStatement ps = c.prepareStatement("INSERT INTO users(id, name, password) VALUES (?, ?, ?)");

        ps.setString(1, user.getId());
        ps.setString(2, user.getName());
        ps.setString(3, user.getPassword());

        return ps;
    }
}

DAO가 깔끔해졌다.

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

코드의 문제점

  • DAO 메소드마다 새로운 StatementStrategy 구현 클래스를 만들어야 함
  • User와 같은 부가적인 정보가 있는 경우, 이를 위해 생성자와 저장해 둘 인스턴스 변수를 만들어야 함

로컬 클래스

StatementStrategy 전략 클래스를 UserDao 내부의 클래스로 정의

  • DeleteAllStatement와 AddStatement는 UserDao에서 밖에 사용하지 않으며, UserDao 메소드 로직에 강하게 결합되어 있으므로 내부 클래스로 정의해도 큰 문제가 없다.
  • UserDao가 아닌 UserDao의 메소드 내부에 클래스를 생성한다.

public class UserDao {
    // ...

    public void add(User user) throws SQLException {
        class AddAllStatement implements StatementStrategy {
            User user;

            public AddAllStatement(User user) {
                this.user = user;
            }

            @Override
            public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
                PreparedStatement ps = c.prepareStatement("INSERT INTO users(id, name, password) VALUES (?, ?, ?)");

                ps.setString(1, user.getId());
                ps.setString(2, user.getName());
                ps.setString(3, user.getPassword());

                return ps;
            }
        }

        StatementStrategy st = new AddAllStatement(user);
        jdbcContextWithStatementStrategy(st);
    }

    public void deleteAll() throws SQLException {
        class DeleteAllStatement implements StatementStrategy {
            @Override
            public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
                PreparedStatement ps = c.prepareStatement("DELETE FROM users WHERE 1=1");
                return ps;
            }
        }

        StatementStrategy st = new DeleteAllStatement();
        jdbcContextWithStatementStrategy(st);
    }
}

중첩 클래스
다른 클래스 내부에 정의되는 클래스

중첩 클래스의 종류

  1. 스태틱 클래스
  2. 내부 클래스
    • 멤버 내부 클래스(member inner class) : 오브젝트 레벨에 정의
    • 로컬 클래스(local class) : 메소드 레벨에 정의
    • 익명 내부 클래스(anonymous inner class) : 선언된 위치에 따라 다름

로컬 클래스의 장점

  • add() 메소드 내부에서 PreparedStatement 생성 로직을 함께 볼 수 있음
  • 클래스가 내부 클래스이므로 자신이 선언된 곳의 정보에 접근이 가능
  • add() 메소드의 user 변수를 AddStatement에서 직접 사용 가능
  • 단, 외부 변수는 반드시 final로 선언하여야 함
public class UserDao {
    // ...
    public void add(final User user) throws SQLException {
        class AddAllStatement implements StatementStrategy {
            @Override
            public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
                PreparedStatement ps = c.prepareStatement("INSERT INTO users(id, name, password) VALUES (?, ?, ?)");

                ps.setString(1, user.getId());
                ps.setString(2, user.getName());
                ps.setString(3, user.getPassword());

                return ps;
            }
        }

        StatementStrategy st = new AddAllStatement();
        jdbcContextWithStatementStrategy(st);
    }
    // ...
}

익명 내부 클래스

좀 더 간결하게 만들어보자

  • AddStatement 클래스는 add() 메소드에서만 사용
  • 클래스 이름도 제거할 수 있다.

익명 내부 클래스

  • 이름을 갖지 않는 클래스
  • 클래스 선언과 오브젝트 생성이 결합된 형태
  • 상속할 클래스나 구현할 인터페이스를 생성자 대신 사용한 형태
  • 클래스 재사용 필요가 없고, 구현한 인터페이스 타입으로만 사용할 경우에 유용
public class UserDao {
    public void add(final User user) throws SQLException {
        StatementStrategy st = new StatementStrategy() {
            @Override
            public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
                PreparedStatement ps = c.prepareStatement("INSERT INTO users(id, name, password) VALUES (?, ?, ?)");

                ps.setString(1, user.getId());
                ps.setString(2, user.getName());
                ps.setString(3, user.getPassword());

                return ps;
            }
        };

        jdbcContextWithStatementStrategy(st);
    }
}

익명 내부 클래스의 오브젝트는 한 번만 사용하면 되기 때문에 변수에 담아두지 말고 jdbcContextWithStatementStrategy() 메소드의 파라미터에서 바로 생성해도 무관 (deleteAll()도 동일)

public class UserDao { 
     // ... 
     public void add(final User user) throws SQLException { 
         jdbcContextWithStatementStrategy( 
             new StatementStrategy() { 
                 @Override 
                 public PreparedStatement makePreparedStatement(Connection c) throws SQLException { 
                     PreparedStatement ps = c.prepareStatement("INSERT INTO users(id, name, password) VALUES (?, ?, ?)");

ps.setString(1, user.getId()); ps.setString(2, user.getName()); ps.setString(3, user.getPassword()); return ps; } } ); } public void deleteAll() throws SQLException { jdbcContextWithStatementStrategy( new StatementStrategy() { @Override public PreparedStatement makePreparedStatement(Connection c) throws SQLException { PreparedStatement ps = c.prepareStatement("DELETE FROM users WHERE 1=1"); return ps; } } ); } // ... }

' Spring > 토비의 스프링 3.1' 카테고리의 다른 글

3.5 템플릿과 콜백  (0) 2019.01.23
3.4 컨텍스트와 DI  (0) 2019.01.22
3.2 변하는 것과 변하지 않는 것  (0) 2019.01.18
3.1 다시보는 초난감 DAO  (0) 2019.01.17
3.0 템플릿  (0) 2019.01.17