시작한지 얼마나 됐다고 중지하냐고 하면... 할말이없는데


일단 개발 쪽 공부할 여건이 안될 것같아서... 


급하게 필요로하는 프레임워크쪽 먼저 공부를 해야할 것 같네요


일단 영어도 있구요.


ㅎㅇㅅ...

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

[토비 vol1] 4.1~4.3 예외  (0) 2017.07.26
[토비 vol1] 3.5~3.7 템플릿(2)  (0) 2017.07.26
[토비 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




4.1 사라진 SQLException


jdbcTemplate 로 바꾼후에 thorws SQLException을 없애도 에러가 발생하지 않는 것을 볼 수 있다. catch블록으로 예외를 잡은 이후에 아무런 처리를 하지 않았기 때문에 그대로 예외가 소멸해버린 케이스이다.

 

 예외를 처리할 때 반드시 지켜야 할 핵심 원칙은 한가지다. 모든 예외는 적절하게 복구되던지 아니면 작업을 중단시키고 운영자 또는 개발자에게 분명하게 통보돼야 한다. 


 예외의 종류와 특징

- Error

java.lang.Error클래스의 서브클래스들이다. 시스템에 뭔가 비정상적인 상황이 발생했을 경우에 사용되는데 애플리케이션 코드에서 잡으려고 하면 안된다. 애플리케이션단에서 처리할 방법이 없기 때문이다.



- Exception과 체크 예외

Exception클래스는 checked exception과 unchecked exception으로  구분되는데 전자는 RuntimeException클래스를 상속하지 않은 것들이고, 후자는 RuntimeException을 상속한 클래스들을 말한다.

일반적으로 예외라고하면 RuntimeException을 상속하지 않은 체크예외라고 생각하면 되는데, catch문으로 잡든지 아니면 throws를 정의해서 메소드 밖으로 던져야 한다. 그렇지 않으면 컴파일 에러가 발생한다.


- RuntimeException과 uncheck/runtime exception

java.lang.RuntimeException 클래스를 상속한 예외들은 명시적인 예외처리를 강제하지 않기때문에 uncheckException이라고 불린다. 이런 uncheck exception들은 굳이 catch문으로 잡거나 throws로 던지지 않아도 된다.(물론 던져도 된다.) 대부분 개발자의 부주의로 발생하는 에러로 조건을 세심히 체크하면 피할 수 있는 에러들이다.


예외처리 방법

- 예외 복구

 예외상황을 파악하고 문제를 해결해서 정상 상태로 돌려 놓는 방법이다. 예를들면 특정 파일에 접근했으나 해당 파일이 존재하지 않아 IOException이 발생했을 때, 해당 예외를 감지하고 상황을 사용자에게 알린 후, 다른 파일에 접근하도록 해서 예외상황을 해결한 경우가 예외 복구 방법이다.


- 예외처리 회피

예외를 자신이 담당하지 않고 호출한 쪽으로 던져버리는 경우에 해당한다. 당연히 catch로 잡아서 외부에서 잡을 수 있도록 다시 throw해줘야한다. 


- 예외 전환

예외 회피와 비슷하게 자신이 담당하지 않고 호출한 쪽으로 던져버리는 경우이지만, 차이점은 발생한 예외를 적절한 다른 예외로 바꿔서 던지는 경우이다. 예외전환의 목적은 두가지인데 첫번째로, 불필요한 catch/throws를 줄여주는 용도이고 두번째는 예외를 좀더 의미있고 추상화된 예외로 바꿔서 던져주기 위함이다.



check 예외는 복구할 수 없는 환경에서도 무분별하게 사용되어 예외처리를 짜증나게 만들었다. 요즘 트렌드는 복구할 수 없는 환경이 발생할 가능성이 높은 예외들은 uncheck 예외로 처리하는 경향이 많다. 


애플리케이션 예외 : 시스템 또는 외부의 예외상황이 원인이 아니라 애플리케이션 자체의 로직에 의해 의도적으로 발생시키고, 반드시 catch해서 무엇인가 조치를 취하도록 요구하는 예외. 일반적으로 이러한 경우에는 복구 가능한 예외들이 대다수고 따라서 일반적으로 의도적인 체크 예외를 만든다. 


 예를 들어보자면 고객이 은행 계좌에서 출금을 요청했는데 은행 계좌에 잔고가 없는 경우라고 해보자. 일반적으로 C에서는 이런 경우에 특별한 값을 리턴하게 만들었다. -1이나 -999등이 이에 해당한다. 하지만 이런 식의 코드는 이해하기 어렵고 실수를 유발 할 수 있다. 이런 경우에 애플리케이션 예외를 발생시켜 예외에 관한 자세한 정보를 넘겨주면 깔끔하게 처리 해 줄 수 있을 것이다. 



그렇다면 SQLException은 복구 가능한 예외인가에 관해서 이야기 해보도록 하자. SQL문이 잘못됬거나 DB서버가 다운 됐거나, DB커넥션 풀이 꽉찼거나, 제약조건을 위반했거나... 발생하는 대부분의 예외들이 복구 불가능한 예외들이다.( 코드를 수정해야하거나 시스템적으로 만져야하는 경우이다.) 따라서 이러한 복구 불가능한 예외들은 uncheck 예외 즉, runtimeException으로 전환해줘야한다.

 스프링의 JdbcTemplate는 이 예외처리 전략을 따르고 있는데, JdbcTemplate안에서 발생한 모든 SQLException을 런타임 예외인 DataAccessException으로 전환해서 던져준다. UserDao메소드에서 꼭 필요한 경우에만 런타임 예외인 DataAccessException을 잡아서 처리하면 되고 그 외의 경우는 무시해도 된다. 



4.2 예외 전환

 JDBC에는 한계가 존재하는데 첫번째는 비표준 SQL을 다룰 수 없다는 것이다. 아무래도 여러 데이터베이스를 범용적으로 커버하기 위해서는 표준에서 벗어난 SQL을 JDBC에 담을 수는 없었을 것이다.

 정말 재밌게도 두번째 한계는 SQLException이 데이터베이스에 종속이라는 점이다. JDBC는 SQLException하나만을 던지는데 정확히 어떤 예외가 발생한 것인지 확인하기 위해서는 SQLException의 에러코드를 확인해야만 한다. 이 에러코드는 데이터베이스마다 다르기 때문에 데이터베이스를 변경하게 될 경우, 코드를 변경해야하는 상황이 발생할 것이다. 데이터베이스의 에러상태코드가 표준화 되어 있기는하지만 DB의 JDBC드라이버에서 SQLException을 담을 상태코드를 정확하게 만들어주지 않는다는 문제도 있다. 즉, 아무래도 상태코드를 믿고 코드를 작성하는 것은 위험할 수 있다.


DB에러 코드 매핑을 통한 전환 

 각각의 데이터베이스 회사들은 에러 코드 매핑 파일을 제공한다. 이 맵핑 파일은 상태코드와 상태코드가 뜻하는 바를 담고 있는데, JdbcTemplate는 드라이버의 DB메타 정보를 참고해서 적절한 예외를 던져준다.이 예외들은 모두 DataAccessException 계층 구조의 예외로 포장해서 던져준다. 따라서 JdbcTemplate을 이용한다면 JDBC에서 발생하는 DB관련 예외는 거의 신경 쓰지 않아도 된다. 예를들자면 add메소드의 예외, 즉 키 중복 예외의 경우에는 DataAccessException의 서브 클래스인 DuplicateKey Exception으로 매핑되서 던져진다. 아래는 코드 예시다.


1
2
3
4
5
6
7
8
9
public void add(final User user) throws DuplicateUserIdException{
    try {
        jdbcTemplate.update("insert into users(id, name, password) values(?,?,?)",
                user.getId(), user.getName(), user.getPassword());
    }
    catch(DuplicateKeyException e){
        throw new DuplicateUserIdException(e);
    }
}
cs




이 이후에 설명되는 내용들은 특정 Dao가 db에 종속적인 부분이 있음을 알리고, 이 부분을 디비에 종속적인 부분을 따로 분리하는 방법을 소개한다. 


하지만 나는 아직 그정도 레벨은 아니라고 생각해서 읽어만 봤다(실제로 데이터베이스를 변경하면서 테스트할 여력도 없다. 시간이없엉!)



소스코드 : https://github.com/choiking10/spring_test/tree/vol1_4.1_4.3/lifeignite



아직 작은 서비스에 관해서 생각하고 있어서 인지, 데이터베이스를 변경하는 큰 작업..정도는 손으로해도 되지 않을까하는 안일한 마음이 있다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ


언제나 개발할 때 , 미래를 생각하는 것도 중요하지만 어느정도 퍼포먼스가 나와야 한다고 생각하는데, 일단 데이터베이스 변경은 내게 있어서 와닿지가 않아서... 인덱스만 걸어두고 언제가 필요할 때 쯤 찾아봐야겠다... 

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

스프링 공부 일시 중지  (0) 2017.08.04
[토비 vol1] 3.5~3.7 템플릿(2)  (0) 2017.07.26
[토비 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

공부하기 싫다. 





3.5 템플릿과 콜벡


 우리는 지금까지 전략패턴을 적용하고 익명 내부 클래스를 활용한 방식으로 코드를 작성해왔다. 이런 방식을 스프링에서는 템플릿/콜백 패턴이라고 부른다. 

이 때, 템플릿은 미리 만들어둔 모양이 있는 툴을 가리킨다. 

콜백은 실행되는 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트를 말한다. 이 때, 이 오브젝트는 파라미터로 전달되지만 값을 참조하기 위함이아니라 특정 로직을 담은 메소드를 실행시키기 위해 사용한다. 


템플릿/콜백의 특징

1) 일반적으로 단일 메소드 인터페이스를 사용한다. 다만, 여러가지 종류의 전략을 사용해야한다면 하나이상의 코백 오브젝트를 사용할 수 있다.

2) 콜백 인터페이스의 메소드에는 보통 파라미터가 있는데, 이 파라미터는 템플릿의 작업 흐름 중에 만들어지는 컨텍스트 정보를 전달 받을 때 사용한다.


콜백의 분리와 재활용

우리가 구현해온 탬플릿/콜백 패턴은 익명 내부클래스를 통해서 작성된 코드이다보니 적지 않게 중첩된 괄호가 만들어지게된다. 이 부분을 분리해 보도록하자.


1
2
3
4
5
6
7
8
9
10
11
public void deleteAll() throws SQLException{
    executeSql("delete from users");
}
public void executeSql(final String query) throws SQLException{
    this.jdbcContext.workWithStatementStrategy(new StatementStrategy() {
        @Override
        public PreparedStatement makePreParedStatement(Connection c) throws SQLException {
            return c.prepareStatement(query);
        }
    });
}
cs


애초에 필요한 것은 sql문장 뿐이었으니 위처럼 익명 클래스를 만드는 작업을 분리를 해서 재활용해보도록 하자. 또한, 이 executeSql은 UserDao뿐만 아니라 다른 Dao에서도 생성 될 수 있다. 따라서 우리가 이전에 만들어둔 JdbcContext클래스로 옮기도록하자


deleteAll 메소드가 더 간결하고 보기 편해졌다. 


 * * * 예제 * * *

 파일에서 라인별로 숫자를 입력받아 더한 후 출력하는 것을 템플릿 콜백으로 작성하라
 파일에서 라인별로 숫자를 입력받아 곱한 후 출력하는 것을 템플릿 콜백으로 작성하라





이제 JdbContext를 버리고 스프링이 제공하는 JDBC 코드용 기본 템플릿을 사용해보자. 클래스명은 JdbcTemplate이다


1
2
3
4
5
public void setDataSource(DataSource dataSource){
    this.dataSource = dataSource;
    
    jdbcTemplate = new JdbcTemplate(dataSource);
}
cs


다음과 같이 설정한 후 deleteAll에 적용해보도록 하자.

1
2
3
4
5
6
7
8
public void deleteAll() throws SQLException{
    jdbcTemplate.update(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
            return connection.prepareStatement("delete from users");
        }
    });
}
cs


더 편하게 다음과 같이 사용할 수도 있다.

1
2
3
public void deleteAll() throws SQLException{
    jdbcTemplate.update("delete from users");
}
cs


add 메소드는 다음과 같이 편하게 구현할 수 있다.



1
2
3
4
public void add(final User user) throws ClassNotFoundException, SQLException{
    jdbcTemplate.update("insert into users(id, name, password) values(?,?,?)",
            user.getId(), user.getName(), user.getPassword());
}
cs



getCount는 다음과 같이 구현할 수 있다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int getCount() throws SQLException{
    return this.jdbcTemplate.query(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
 
            return connection.prepareStatement("select count(*) from users");
        }
    }, new ResultSetExtractor<Integer>() {
        @Override
        public Integer extractData(ResultSet resultSet) throws SQLException, DataAccessException {
            resultSet.next();
            return resultSet.getInt(1);
        }
    });
}
cs


JdbcTemplate은 이보다 더 간단한 방식으로 처리하는 메소드로 queryForInt()라는 친구를 제공한다고 써저 있으나 spring의 3.2.2버전에서 deprecated됐다. 

 


1
2
3
public int getCount() throws SQLException{
    return this.jdbcTemplate.queryForObject("select count(*) from users",Integer.class);
}
cs


대신 위와 같이 구현이 가능하다.


get 메소드는 이보다 좀더 복잡한데, qureyForObject를 통해서 구현이 가능하다



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public User get(String id) throws ClassNotFoundException, SQLException{
    return this.jdbcTemplate.queryForObject(
            "select * from users where id = ?",
            new Object[]{id},
            new RowMapper<User>() {
                @Override
                public User mapRow(ResultSet resultSet, int i) throws SQLException {
                    User user= new User();
                    user.setId(resultSet.getString("id"));
                    user.setName(resultSet.getString("name"));
                    user.setPassword(resultSet.getString("password"));
                    return user;
                }
            });
}
cs



원래 get 메소드는 EmptyResultDataAccessException을 던지게 만들어져 있었는데, 위의 queryForObject가 조회결과가없을 경우에는 해당 Exception을 발생시켜준다.


여기까지 테스트하고 확인!



 * * * 예제 * * *

 getAll을 구현해보자!

 TDD할 것 (즉 테스트 먼저 생성 후,  


이 이후에 네거티브 테스트에 관하여 나온다. 요약해보자면, 테스트를 만들 때, 부정적인 테스트를 먼저 만들라는 것이다. 예를 들자면, 나이에 음수값을 넣어보거나 생일에 문자를 넣어보는 등에 관련 된 테스트에 관하여 중요하게 설명하고 있다. 



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



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

스프링 공부 일시 중지  (0) 2017.08.04
[토비 vol1] 4.1~4.3 예외  (0) 2017.07.26
[토비 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

.



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


하루에 장 하나씩하는 것도 벅차네




junit의 작동방식의 특성상 테스트는 before -> test -> after 식으로 테스트 개수만큼 실행된다. 이게 spring의 경우 문제가 있는데 applicationContext가 테스트 개수만큼 생성됐다 소멸됐다 하는 것이다. 테스트 규모가 적을 경우 이는 문제가 없지만, 프로그램의 규모가 커지면 테스트하는 속도를 늦출 수 있다.  따라서 이 어플리케이션 컨텍스트를 단 한번만 생성할 수 있도록 스프링은 테스트를 지원한다.



1
2
3
4
5
6
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml")
public class UserDaoTest {
    @Autowired
    private ApplicationContext context;
cs


@RunWith는 Junit 프레임워크의 테스트 실행방법을 확장할 때 사용하는 애노테이션인데 SpringJUnit~ 는 이를 스프링용으로 확장하여 JUnit이 테스트를 진행하는 중에 테스트가 사용할 애플리케이션 컨텍스트를 만들고 관리하는 작업을 진행해준다. 

@ContextConfiguration은 설정파일의 위치를 지정한 것이다. 


이렇게 만들어진 어플리케이션 컨텍스트는 각 테스트가 여러개 진행되어도 단 하나만 존재하게 되며, 뿐만 아니라 다른 클래스에서 똑같은 어플리케이션 컨텍스트를 만들어서 테스트를 하더라도 단 하나만 존재하게 된다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml")
public class UserDaoTest {
    @Autowired
    private ApplicationContext context;
}
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml")
public class GroupDaoTest {
    @Autowired
    private ApplicationContext context;
}
 
cs


즉 위와같이 클래스가 다른 테스트 에서도 애플리케이션 컨텍스트는 단 하나만 생성된다.


이때 사용되는 어노테이션 @Autowired라는 친구를 변수에 붙이면 테스트 컨텍스트 프레임워크는 변수타입과 일치하는 컨텍스트 내의 빈을 찾아 해당 변수에 넣어준다. 애플리케이션 컨텍스트는 최초 초기화시에 자기자신을 빈으로 등록하므로 위와 같은 코드가 정상 작동하는 것이다.

 위처럼 UserDao도 @Autowired를 통해서 자동으로 빈을 DI받을수있는데 단, 같은 타입의 빈이 두 개 이상 있는 경우에는 어떤 빈을 가져올지 결정할 수 없다.이럴 경우 예외가 발생하게된다. 

 테스트의 경우에도 되도록 느슨하게 결합하기 위해 인터페이스를 활용하는게 좋지만, 필요에 따라 특정 타입의 구체화된 인스턴스를 의도적으로 테스트할 필요성이 있을 수 있기에 밀접한 관계를 가져도 상관없다.


 테스트에서 때로는 의존관계를 수동으로 DI해줘야 할 경우가 있다. 예를 들자면, 우리가 작성한 테스트가 실 서비스상에서 제공된다고 가정해보자. 그렇다면 deleteAll을 실행하는 순간 모든 데이터가 날아가 버릴 것이다. 이건 너무 위험천만한 행위이므로 우리가 임의적으로 testdb에서 실험을 진행할 수 있다. 이 때, 우리가 수동으로 DataSource를 만들어서 테스트디비에 접속하게 만들 수 있다. 다만, 이런 방식의 테스트는 애플리케이션 컨텍스트를 직접 수정하는 것이므로 위험할 수 있다. 애플리케이션은 한번 생성되어 다른 테스트들에게도 영향을 줄 수 있기 때문이다. 

 이럴 때 사용하는 어노테이션이 있는데 바로 @DirtiesConext이다. 간단하게 설명하자면 해당 테스트에서 컨텍스트를 더럽힐 것이므로 테스트 종료 후 새로운 어플리케이션컨텍스트를 생성하라는 어노테이션이다. 이는 클래스에도, 메소드에도 붙일 수있다. 

 하지만 이러는 것보다 그냥 테스트용 설정 파일(xml)을 만드는 것을 추천한다.(아니 왜설명한거야... )



2.5 학습 테스트

 학습 테스트는 자신이 만든 코드에 관한 테스트가 아니라 앞으로 사용하고자하는 api나 프레임워크의 기능을 테스트하는 것을 말한다. 이런 테스트는 검증에 목적이 있지 않고, 해당 기술의 학습에 목적이 있다. 

 

 길게 써놨는데 간략히 요약하자면, 레퍼런스가 될 수도 있고, 이미 작성되어있는 학습 테스트로 부터 레퍼런스 이상의 사용법을 배울 수 있으며, 해당 기술의 테스트방법을 공부할 수 있다는 의견인것 같다. 


 테스트 방법으로 동등분할과 경계값 분석이 있다. 동등 분할은 같은 결과를 내는 값의 범위를 구분해서 대표값으로 테스트 하는 것을 말하고, 경계값 분석은 0이나 그 주변 값 또는 최대값, 최소값등으로 테스트하는 것을 의미한다.


소스코드 : https://github.com/choiking10/spring_test/tree/vol1_2.4_2.6/lifeignite

블로그 쓰면서 공부하려니 넘나 힘든 것... 


이게 나중에 도움이 되겠지?... 


딱히 누구 보라고 작성하는 건 아니긴 한데... 흠... 




2.1 <- 테스트의 필요성이다. 

당욘한거죠!

넘어가


2.2 UserDaoTest

기존의 방식은 테스트의 결과 확인을 직접해야하는 번거로움이 있었다. 이것을 이제 테스트의 결과를 확인하기 쉽도록 UserDaoTest를 수정해보도록 하자



1
2
3
System.out.println(user2.getName());
System.out.println(user2.getPassword());
System.out.println(user2.getId()+" 조회 성공");
cs


이게 원래 수정 전 테스트 코드이다.


이 경우에는 실제 조회가 성공했는지 눈으로 확인해야한다. 이걸 자동화 시켜보도록 하자.



1
2
3
4
5
6
7
8
if(!user.getName().equals(user2.getName())){
    System.out.println("테스트 실패 (name)");
}
if(!user.getPassword().equals(user2.getPassword())){
    System.out.println("테스트 실패 (password)");
}else {
    System.out.println("조회 테스트 성공");
}
cs


자동화 성공!

...

별거없다.


2.3 JUnit 테스트로 전환하기

일단 junit을 사용하기위해 dependency를 pom.xml에 추가해주도록 하자.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
</dependency>
cs

그리고 다음을 static import한다 


1
2
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
cs


자 그러면  다음과 같은 모습이 되는데


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
29
30
31
32
33
34
35
36
37
38
39
package lifeignite;
 
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
 
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import user.User;
import user.UserDao;
 
import java.sql.SQLException;
 
public class UserDaoTest {
    public static void main(String[] args) {
        JUnitCore.main("lifeignite.UserDaoTest");
    }
 
    @Test
    public void addAndGet() throws ClassNotFoundException, SQLException{
        ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
 
        UserDao dao = context.getBean("userDao", UserDao.class);
 
        User user = new User();
        user.setId("lifeignite");
        user.setName("광전사");
        user.setPassword("for");
 
        dao.add(user);
 
        User user2 = dao.get(user.getId());
 
        assertThat(user2.getName(),is(user.getName()));
        assertThat(user2.getPassword(),is(user.getPassword()));
    }
}
 
cs


조작을해보면서 성공시와 실패시 어떤 방식으로 뜨는지 체크할 수 있다. 


기존의 방식은 디비에서 직접 추가된 user를 삭제하고, 테스트를 돌리는 방식이었는데 넘나 귀찮다. 따라서 이제 삭제하는 메소드도 만드려고한다. 또 user테이플의 개수를 가져오는 것도 만들고 뚝딱뚝딱



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
public class UserDaoTest {
    @Test
    public void addAndGet() throws ClassNotFoundException, SQLException{
        ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
 
        UserDao dao = context.getBean("userDao", UserDao.class);
 
        dao.deleteAll();
        assertThat(dao.getCount(), is(0));
 
        User user = new User();
        user.setId("lifeignite");
        user.setName("광전사");
        user.setPassword("for");
 
        dao.add(user);
        assertThat(dao.getCount(),is(1));
 
        User user2 = dao.get(user.getId());
 
        assertThat(user2.getName(),is(user.getName()));
        assertThat(user2.getPassword(),is(user.getPassword()));
    }
}
 
cs


중요한 내용은 아니었고...


junit에서 테스트 중에 발생될 것으로 기대하는 예외 클래스를 지정해 줄 수 있다.

@Test(expected=somethingException.class)

이런방식으로 설정해둔 테스트에서는 예외가 발생하면 성공이고 아니라면 실패가 된다.

@Before 어노테이션은 테스트를 시작하기전에 먼저 실행해야하는 메소드를 정의하는데 사용된다.

@After 어노테이션은 테스트가 끝나고 난 후에 실행해야하는 메소드를 정의하는데 사용된다. 


JUNIT에서 테스트를 진행하는 과정을 간단히 정리하자면 아래와 같다.

1) 테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 찾는다.

2) 테스트 클래스의 오브젝트를 하나 만든다.

3) @Before가 붙은 메소드가 있으면 실행한다.

4) @Test가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다

5) @After가 붙은 메소드가 있으면 실행한다.

6) 나머지 테스트 메소드에 대해 2~5번을 반복한다

7. 모든 테스트의 결과를 종합해서 돌려준다.



TDD에 관한 설명이 나오는데 너무 아름다우니까 두번 외치자


테스트를 만들고 테스트에 맞춰서 구현을 한다. 


알고리즘 사이트에서 테스트 케이스 만들고 구현하는 느낌인데 되게 새롭고 참신하고 매력적인 방법인것 같다.

이런방식으로 개발해야지


소스코드 : https://github.com/choiking10/spring_test/tree/vol1_2.1_2.3/lifeignite




평소와 달리 블로그를 쓰면서 공부를 하니 2~3배 더오래걸린다. 장점은 2~3배 더 자세히 하게되는 거 같은데, 똥멍청이라 평소라면 아몰랑 필요없어! 하던 것들을 좀더 자세히 들여다 볼 수 있다는 것 정도인것 같다. 




1.5 스프링의 IOC


우린 저번 포스트에서 자바빈에 관해서 이야기 했었는데 스프링에서의 빈은 또 다르다.

 스프링에서의 빈은 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트로 정의된다. 하지만 뭐, 자바빈 또는 엔터프라이즈 자바빈에서 말하는 빈과 비슷한 오브젝트 단위의 애플리케이션 컴포넌트를 말한다.(라고 써있다. 둘다 자세히 모르는 것들...)


 빈 팩토리는 빈을 생성하고 관계를 설정하는 IoC의 기본 기능에 초점을 맞춘 것

 애플리케이션 컨텍스트는 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC엔진이라는 것에 초점을 맞춘 것


원래 우리가 사용하고 있던 DaoFactory를 스프링의 애플리케이션 컨텍스트가 사용할 수 있도록 변경 할 수 있는데 @Configuration과 @Bean을 이용해서 구현 할 수 있다. 

 @Configuration : 애플리케이션 컨테스트 또는 빈 팩토리가 사용할 설정정보라는 표시

 @Bean               : 오브젝트 생성을 담당하는 IoC용 메소드라는 표시 @Bean을 사용할 경우 해당 메소드의 이름으로 ApplicationContext에 등록된다. 



1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
@Configuration
public class DaoFactory {
 
    @Bean
    public UserDao userDao(){
        UserDao userDao = new UserDao(connectionMaker());
        return userDao;
    }
    @Bean
    public ConnectionMaker connectionMaker(){
        return new NConnectionMaker();
    }
}
cs

 UserDao.class는 일종의 제네릭 함수로 원래 getBean함수는 return을 Object 클래스로 준다. 아래와 같이 클래스를 넘겨주게 되면 불필요하게 케스팅하는 과정을 하지 않아도 되게 만들어준다.(java 5버전 이상에서 제공한다고 한다. )
 

1
2
3
4
5
6
7
8
9
10
public class UserDaoTest {
 
    public static void main(String[] args) throws ClassNotFoundException, SQLException{
        ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
 
        UserDao dao = context.getBean("userDao", UserDao.class);
        // ...
    }
}
 
cs



어플리케이션 컨텍스트의 동작 방식

 @Configuration을 붙인 Factory가 ApplicationContext에 등록되고 이 펙토리 안에 이는 모든 @Bean목록이 applicationContext안의 빈 목록에 들어간다. getBean요청이 오면 해당 펙토리의 Bean을 생성하는 방식을 취하고 있다.




재밌는 방식인듯... 


장점 


     1) 클라이언트가 구체적인 팩토리 클래스를 알 필요가 없다.

     2) ApplicationContext는 종합 IoC 서비스를 제공한다. (이건 나중 돼봐야 알 수 있을 것 같다)

     3) 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다. ( 빈 검색이 이름, 특정한 애노테이션 등으로 검색가능)



1.5을 정리하자면 

 빈 : 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트

 빈 팩토리 : 빈의 생성과 관계설정 같은 제어를 담당하는 IoC오브젝트. BeanFactory는 인터페이스로 getBean과 같은 메소드들이 정의되어 있다.

 애플리케이션 컨텍스트 : 빈 팩토리와 개념은 같다고 보면 된다. BeanFactory를 상속해서 구현한 것이다. BeanFactory가 제공하는 기능에 더해 스프링이 제공하는 추가적인 기능등을 포함한다. 

 설정정보/설정 메타정보 : IoC를 적용하기 위해 사용하는 메타정보다. 
 IoC 컨테이너 : 애플리케이션 컨텍스트나 빈 팩토리를 컨테이너 또는 IoC컨테이너라고한다. 


1.6 싱글톤 레지스트리와 오브젝트 스코프
  스프링은 빈 오브젝트를 특별한 설정을 하지 않는한 싱글톤으로 생성한다. 설명의 흐름상 싱글톤을 설명해야할 것 같지만 내가 알고있는 개념이므로 설명하지않는다. (ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ ㅈㅅ)

싱글톤의 한계
 private생성자를 갖고 있기 때문에 상속할 수 없다.

 테스트하기가 힘들고, 때에 따라서는 테스트 자체가 불가능 할 수도 있다.

 하나의 오브젝트임을 보장하지 못한다. 즉, 서버환경에서 클래스 로더를 어떻게 구성하고 있느냐에 따라서 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수 있다. 심지어 여러개의 JVM에 분산설치가 되는 경우에는 각각 독립적으로 오브젝트가 생겨 싱글톤으로서의 가치가 떨어진다.

싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.

이처럼 순수한 싱글톤 패턴은 문제가 많아서 스프링은 이에 대한 해결책으로 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다. 이것이 싱글톤 레지스트리다.


어찌 됐든 싱글톤으로 빈이 관리되기 때문에 몇가지 주의점이 생기는데

기본적으로 stateless방식으로 관리되어야 한다는 것이다. 즉, 상태정보를 내부에 저장하지 않아야한다. 만약 빈이 상태 정보를 가지고 있을 경우 멀티 쓰레드환경에서 싱글톤 오브젝트의 인스턴스 변수를 수정하게 되는데, 이는 매우 위험한 행위다. => 로컬 변수와 리턴값, 파라미터 등을 이용하자.


현재 구현되어 있는 UserDao는 stateless하다.


빈의 생성, 존재 하는 범위인 스코프는 10장에서 다룬데요. <- 따라서 지금은 다루지 않는다. 



1.7 의존관계 주입 (DI: Dependency Injection)

 의존관계란? A가 B에 의존하고 있다는 의미는 B가 변하면 A가 영향을 받는다는 의미다. 일반적으로 의존관계는 방향성이 있는데 A가 B에 의존적이라고 하더라도 일반적으로 B는 A에 의존적이지 않다. 대표적인 예로 A의 내부에서 B를 사용할 경우를 들 수 있다. 

 의존 오브젝트란? 클래스상에서의 의존관계가 아니라 런타임시에 의존관계를 맺는 대상


 의존관계 주입의 세 가지 조건

        1) 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. (인터페이스에만 의존하고 있어야한다.) 

2) 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제3의 존재가 결정한다.

3) 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공해줌으로서 만들어진다. 



1
2
3
    public UserDao(ConnectionMaker connectionMaker){
        this.connectionMaker = connectionMaker;
    }
cs
UserDao의 생성자가 의존관계를 주입받기 위한 코드이다.

이 때, DaoFactory가 의존관계(제 3의 존재)를 주입하게 된다. 


의존관계 주입 말고 검색이라는 친구도 있는데 아래 코드처럼 getBean을 통해서 검색을 통해 가져오는 것을 말하는데 아래의 코드는 의존관계 검색을 통해 구현한 UserDao의 생성자이다.


1
2
3
4
public UserDao(ConnectionMaker connectionMaker){
        AnnotationConfigApplicationContext context = 
new AnnotationConfigApplicationContext(DaoFactory.class);
        this.connectionMaker = context.getBean("connectionMaker", ConnectionMaker.class);
    }
cs


그다지 이쁜 모습은 아닌데, main에서 context를 가져와서 테스트한 UserDaoTest의 경우가 의존관계 검색의 예라고 볼 수 있다. 


 의존관계 검색과 의존관계 주입의 가장 큰 차이점은 검색은 검색하는 오브젝트가 스프링의 빈일 필요가 없다는 점이고, 주입의 경우 반드시 컨테이너를 만드는 빈 오브젝트여야 한다는 것이다. (UserDao와 ConnectionMaker모두 bean이다.) 왜냐하면 주입하기 위해서는 UserDao가 생성과 초기화 권한을 위임해야하기 때문이다. 


의존관계를 주입하기위해 우리는 생성자를 이용했는데 사실 뭐 수정자 메소드를 이용해도 상관없다. 사실, 이게 더 자주 사용된다.(setter메소드를 말한다.)



1.8 XML을 이용한 설정

 사실 DaoFactory를 작성하는 일은 반복적이고 틀에 박힌 것으로 굳이 자바로 관리할 필요는 없다. 따라서 이것을 XML로 관리할 수 있는 편의를 스프링은 제공해주는데 

@Configuration : <beans>

@Bean               : <bean>

에 대응한다

여기에 추가로 property라는 친구가 있는데 이 property라는 친구에는 name과 ref를 속성으로 가지는데 name은 DI에 사용할 수정자 메소드의 프로퍼티 이름이며(setter메소드) ref는주입할 오브젝트를 정의할 빈의 ID다. 


DaoFactory를 xml로 수정하면 아래와 같이 만들어진다.



1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="connectionMaker" class="user.NConnectionMaker"></bean>
    <bean id="userDao" class="user.UserDao">
        <property name="connectionMaker" ref="connectionMaker"/>
    </bean>
</beans>
cs


XML은 텍스트이기 때문에 찾아 바꾸기를 해야한다. 따라서 실수할 가능성이 높으므로 이름을 잘 정하도록 하자



ConnectionMaker 대신에 DataSource로 바꾸기


토비에 나와있는 예제는  connectionMaker 대신에 DataSource로 바꾸는데 그대로 따라하다보면 몇가지 에러를 발생시킨다. 



1
2
   <bean id="dataSource"
          class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
cs


여기서 에러가 발생하는데 class를 찾을 수 없다고 나온다.


이 때, pom.xml에 dependency를 추가해주면 해결된다( auto import 를 사용하면 자동으로 잡아준다 )

1
2
3
4
5
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
cs

여기서 $(~) 는 버전명이다. 나의 경우에는 4.3.9.RELEASE 를 사용했다.



여기까지의 소스코드 : https://github.com/choiking10/spring_test/tree/vol1_1.5_1.9/lifeignite

'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.1~1.4 오브젝트와 의존 관계(1)  (0) 2017.07.24
spring 시작하기  (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

토비 3.1 기준으로 진도를 나가고, 예제를 따라해보며 공부해볼 생각이다. 


jetbrain의 intelli j를 IDE로 쓸 예정인데 대학생 인증을하면 무료로 사용할 수 있다. 


학생 링크 : https://www.jetbrains.com/student/


이 글을 보고 있다면 개발자일테니 설치 도중에 생기는 고통은 자신의 몫이다. 

( 게다가 딱히 고통 받을 일도 없다.)


다만, 학생이 아니라면 젯브레인에서 유료로 사서 작성해야 한다 (스프링 프로젝트를 프로버전에서만 제공한다.)


물론 굳이 그럴 필요 없을 수도 있는데...


나는 자바도, 스프링도, JSP도 뭐든 간에 다 허접이니까 친절한 프로버전을 사용할꺼야


이 책자체가 3.1버전기준이라 고통이라는 고통은 다 받을 것 같아서 블로그하는 의미가 있지 않을까 싶다



내가 다 본건 아닌데 대충 봤을 때, 토비 3.1의 vol1은 기본 지식(디자인 패턴 + 철학?) 같은 것들을 다루고


제대로 된 스프링은 vol2부터 다루는 것 같다. 


공부할 겸사겸사할 꺼니까 vol1부터 하도록 하겠다.

+ Recent posts