.



3.1 예외처리

 기존에 작성했던 UserDao는 심각한 결함이 있는데, 바로 예외처리를 해주지 않았다는 것이다. 


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
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){
 
            }
        }
    }
}
cs


간단히 설명하자면 sql관련 exception이 발생했을 시에 공용리소스인 Connection이 close되지 않는 심각한 문제가 발생할 수 있다는 것이다. 따라서 공용리소스를 close하는 부분에 예외처리를 해주도록 하자. 현재는 catch부분이 비어 있으나 추후에 개발에 들어갈 때에는 이곳에서 로깅을 하는 코드를 작성하거나 하곤 하므로 남겨두도록 하자.

 

3.2 변하는 것과 변하지 않는 것

 예외를 처리했음에도 이 코드는 문제가 있다. 수많은 UserDao의 데이터 접근 로직들은 위와같은 코드를 포함해야 할 것이며, 그렇게 된다면 우리는 수많은 복잡한 try catch finally 문을 마주해야할 것이다. 이는 수정하기 곤란하고, 누군가가 필요한 부분을 찾아 수정한 후에도 그 복잡성 때문에 문제가 발생하지 않으니라는 보장이 없다.  한마디로 폭탄돌리기식의 코드라는 것이다.


이 코드를 이쁘게 만들어 보도록 하자. 


그러기 위해서 변하는 것과 변하지 않는 것으로 나누어보았다


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
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){
 
            }
        }
    }
}
cs


회색 부분은 변하지 않는 부분이고 중간에 파란색 부분은 변하는 부분이다. 이를 효과적으로 분리하기 위해서는 어떻게 해야할까?
토비에서 방법중 하나로 제시한 것으로 템플릿 메소드 패턴을 사용하는 것이다. 하지만 이 방식은 하나의 Dao로직당 하나의 상속 클래스를 만들어야한다. 매우 비효율적인 구조라고 할 수 있다. 

대신에 우리는 전략 패턴을 사용해서 이 것을 분리할 것이다. 

여기서 회색부분은 언제나 변하지 않는 부분이고 우리는 이것을 context라고 부를 것이다. 이것을 이제 새로운 메소드 jdbcContextWithStatementStrategy로 분리할 것이다.

우리의 변하는 부분, 즉 prepareStatement를 처리하는 부분은 StatementStrategy라는 인터페이스로 빼 두고 우리가 필요할 때 이 부분만을 갈아치울 것이다.



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
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){
 
            }
        }
    }
}
cs



이제 복잡한 try catch finally에 관한 내용만을 담은 코드를 빼낼 수 있었다. 

이제 바뀐 deleteAll 메소드를 보자


1
2
3
4
5
6
7
8
public class DeleteAllStatement implements StatementStrategy {
    @Override
    public PreparedStatement makePreParedStatement(Connection c) throws SQLException{
        PreparedStatement ps = c.prepareStatement("delete from users");
 
        return ps;
    }
}
cs


1
2
3
4
public void deleteAll() throws SQLException{
    StatementStrategy st = new DeleteAllStatement();
    jdbcContextWithStatementStrategy(st);
}
cs


깔끔...


3.3 jdbc 최적화


여기서 conext는 statementStrategy 타입의 전략 오브젝트를 제공 받고 컨테스트 내에서 작업을 수행한다. 이 때, 제공받은 전략 오브젝트는 PreparedStatement생성이 필요한 시점에 호출해서 사용한다. 

 이 때, deleteAll 메소드는 클라이언트가 되는데, 전략 오브젝트를 만들고 컨텍스트를 호출하는 책임을 지고 있다. 자세한 것은 코드를 보도록 하자. 


위의 방식은 뭐 코드도 이쁘고 다좋은데, 클래스를 너무 많이 생성해야한다. 그게 단지 UserDao에서만 사용되는 것임에도 불구하고 다른 클래스에서 조차 노출된다는 문제가 있다. 이를 방지하기 위해 익명 클래스를 이용해서 구현하도록 하자. 



1
2
3
4
5
6
7
8
9
10
public void deleteAll() throws SQLException{
    jdbcContextWithStatementStrategy(new StatementStrategy() {
        @Override
        public PreparedStatement makePreParedStatement(Connection c) throws SQLException {
            PreparedStatement ps = c.prepareStatement("delete from users");
 
            return ps;
        }
    });
}
cs


비슷하게 add를 구현한 모습이다. 변수를 final로 받으면 익명 클래스로 전달해서 사용할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void add(final User user) throws ClassNotFoundException, 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;
        }
    });
}
cs


3.4 컨텍스트와 DI


JdbcContext는 사실 UserDao 이외의 다른 Dao에서도 사용할 예정이므로 새로운 클래스로 분리하는게 맞다. 또한 JdbcContext는 dataSource에 의존적이므로 DataSource 타입 빈을 DI받을 수 있게 해줘야한다. 또, 그에 맞게 의존관계를 변경해줘야한다. 소스코드는 이식하는 작업일 뿐이므로 생략하고 의존관계를 변경한 xml만 살펴보도록 하자.



1
2
3
4
5
6
<bean id="userDao" class="lifeignite.user.UserDao">
    <property name="dataSource" ref="dataSource"/>
    <property name="jdbcContext" ref="jdbcContext"/>
</bean>
<bean id="jdbcContext" class="lifeignite.user.JdbcContext">
    <property name="dataSource" ref="dataSource"/>
cs


자이제 테스트를 해보고 정상 작동됨을 확인해보도록하자. 


하지만 이러한 변경은 이전에 진행했던 DI 와는 조금 다르다. jdbcContext는 인터페이스가 아니므로 의존관계가 고정될 수 밖에 없다. 인터페이스를 사용하지 않았으므로 온전한 DI라고 볼 수 없는데, 객체의 생성과 관계설정에 대한 제어권한을 오브젝트에서 제거하고 외부로 위임했다는 IoC개념을 포괄한다. 따라서 DI의 기본을 따르고 있다고 말할 수 있다. 


이 때, JdbcContext를 UserDao와 DI구조로 만들어야하는 이유에 대해서 생각해보자.

1) JdbcContext는 스프링 컨테이너의 싱글톤 레지스트리에서 관리되는 싱글톤 빈이 된다. 실제로 JdbcContext는 stateless함으로 이렇게 관리됨이 옳다

2) JdbcContext가 DI를 통해 다른 빈에 의존하고 있기 때문이다. 알다시피 DI를 위해서는 주입되는 오브젝트와 주입 받는 오브젝트 모두 빈이어야한다.


이 이후에 설명으로 빈으로 만들지 않고 처리하는 여러가지 방식이 나오는데, 넘어가도록 하겠다.







새롭게 알게 된 사실

토비책을 보게되면 중복된 이름이 없을 때에도 멤버변수를 사용할 때, this.맴버변수 형태로 사용하고 있는데 이는 가독성을 높일 수 있다. 클래스나 메소드가 엄청 길어져서 맴버변수인지 클래스 변수인지를 알 수 없을 때, 쉽게 확인할 수 있도록 해준다. 앞으로 이런 방식으로 사용하도록 하자.


소스코드 : https://github.com/choiking10/spring_test/tree/vol1_3.1_3.4/lifeignite

'web > spring' 카테고리의 다른 글

[토비 vol1] 4.1~4.3 예외  (0) 2017.07.26
[토비 vol1] 3.5~3.7 템플릿(2)  (0) 2017.07.26
[토비 vol1] 2.4~2.6 테스트(2)  (0) 2017.07.25
[토비 vol1] 2.1~2.3 테스트(1)  (0) 2017.07.25
[토비 vol1] 1.5~1.9 오브젝트와 의존관계(2)  (0) 2017.07.24

+ Recent posts