.



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

 이 글은 대에 충 훑어보면서 쓰는 글이기 때문에 각종 오류와 잘못된 지식이 포함 될 수 있음을 공지한다



 1장에 나오는 코드는 단순하고 의미없고, 쉬워서 작성하지 않았다. (여러분은 책을 사서 보고 있겠쬬 ㅎㅎ)





1.1장

 DAO 

 Data Access Object DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트


 자바빈

 원래는 비주얼 툴에서 조작 가능한 컴포넌트를 뜻했음. 하지만 자바가 웹 기반쪽으로 선회하면서 자바빈이 인기를 잃기 시작했음. 시간이 흐르면서 자바빈이 남긴 몇 가지 코딩 관례를 뜻하게 됨. 간단하게 이라고 부르기도 함.

 자바빈은 아래와 같은 두가지 관례를 따라 만들어진 오브젝트임

 1) 디폴트 생성자  : 파라미터가 없는 생성자가 존재해야함

 2) 프로퍼티         : getter-setter로 수정 또는 조회 할 수 있도록 만듬


  



1.2장 DAO의 분리

  관심사의 분리 : 관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모이게 하는 것. 관심이 다른 것은 가능한 따로 떨어져서 서로 영향을 주지 않도록 분리하는 것.

 

 DAO에서 필요로하는 관심사들을 나눠보자면.. 

        1. DB와 연결을 위한 커넥션 가져오는 것

2. SQL문의 statement를 만드는 것

3. statement와 connection 오브젝트를 닫아줘서 공유 리소스를 시스템에 돌려주는 것


그래서 이 connection의 분리를 템플릿 메소드 or 펙토리 메소드 패턴으로 분리한다.


템플릿 메소드 패턴

 내가 이해한 바가 맞다면, 변하지 않는 고유의 기능은 super클래스의 메소드로 제공하고, 변하는 기능은 추상 메소드, 혹은 오버라이드 가능한 메소드로 정의해두고, 다른 개발자들에게 오버라이딩해서 사용하는 방식을 이른다. 여기서 반드시 구현해야 하는 메소드는 추상 메소드(abstractMethod) 형태로, 선택적으로 구현해도, 안해도 되는 것은 오버라이드 가능한 메소드형태로 로 제공한다.  구현 안해도 되는 메소드를 훅 메소드(hookMethod)라고 부른다.


펙토리 메소드 패턴 

 멍청해서 이게 뭥미 하고 멍 때리고 있었는데, 기본적인 개념은 어떤 슈퍼클래스가 있고 이 슈퍼 클래스가 처리 골격을 만든다. 하지만 생성은 하지 않는데, 이 생성을 하는 책임은 펙토리에게 있다. 이 펙토리 메소드 페턴의 경우에도 상속을 통해서 구현하게 되는데 상속을 통한 구현은 그닥 좋은 방법은 아니라고 한다. 일반적으로 비슷한 역할을 할 수 있는 전략 패턴을 사용하는게 더 좋다는 이야기이다.

<- 위키꺼



1.3 DAO의 확장

 이 장에서는 전략 패턴으로 DAO를 확장하는 방법을 설명한다. 원래 UserDAO에 존재하던 getConnection을ConnectionMaker 인터페이스로 분리시키는 작업을 설명한다. 


전략 패턴

 펙토리 패턴보다 훨 씬 간결한데 어떤 클래스의 컴포넌트로서 가지고 있고, 필요할 때마다 이 전략 인터페이스를 구현해서 교체하는 방식으로 작동한다. 디자인 패턴 관련해서는 나중에 함 정리하는 글을 써야겠당... 



이 장에서 객체지향 설계 원칙(SOLID)에 관한 설명이 나오는데 그 내용은 다음과 같다.

       1) SRP(The Single Responsibility Principle):       단일 책임 원칙

       2) OCP(The Open Closed Principle):                  개방 폐쇄 원칙

3) LSP(The Liskov Substitution Principle):        리스코프 치환 원칙    

4) ISP(The Interface Segregation Principle):     인터페이스 역전 원칙

5) DIP(The Dependency Inversion Principle):   의존관계 역전 원칙 


요것도 나중에 공부해서 다시 작성해보도록 하겠다... 




1.4 제어의 역전(IoC)

 위에서 각각의 책임별로 찢어 놨던 여러 종류의 클래스들은 그 책임을 분리하기위해서 여러가지로 찢어져버렸고 따라서 제대로 된 역할을 하는 객체를 생성하기위해서는 복잡한 과정을 가져야 한다. 이 때 등장하는데 펙토리라는 친구다. 이 친구는 객체의 생성 방법을 결정하는 역할을 한다. (이 펙토리는 추상 팩토리 패턴이나 팩토리 메소드 패턴의 그 팩토리하고는 조금 다르다. )

 

 이전의 UserDao와 ConnectionMaker라는 친구는 애플리케이션의 핵심적인 데이터 로직과 기술 로직을 담당하고 있었다면 이 factory라는 친구는 컴포넌트의 구조와 관계를 정의한 설계도와 같은 역할을 한다고 볼 수 있다.

 

 제어권의 이전을 통한 제어 관계 역전

 제어의 역전(Inversion of Control)은 한마디로 말하자면 오브젝트가 자신이 사용한 오브젝트를 스스로 선택하지 않는다. 따라서 생성하지도 않는다. 뿐만 아니라 자신이 어디서 사용되는지도 알 수 없다. 이는 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 대문이다.


 프레임워크가 제어의 역전 개념이 적용된 대표적인 기술인데, 라이브러리는 애플리케이션의 흐름을 직접 제어하는 반면 프레임워크는 우리가 작성한 애플리케이션의 코드가 프레임워크에 의해서 사용된다.


실습 소스코드 : https://github.com/choiking10/spring_test/tree/vol1_1.1_1.4/lifeignite/src/main/java/user



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

[토비 vol1] 3.1~3.4 템플릿(1)  (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
spring 시작하기  (0) 2017.07.24

+ Recent posts