Chapter 1

본 내용은 토비의 스프링 3 책의 내용을 정리한 것입니다.

토비의 스프링은 스프링 뿐아니라 객체 지향의 기본 원리, 테스팅, 예외 처리, 디자인 패턴 등 Java 개발자라면 반드시 알아야 하는 내용을 스토리 전개 방식으로 점진적으로 친절하게 설명해주는 명저입니다.

똑같은 내용으로 미국에서 영어로 출간되었다면 Jolt상을 받기에도 충분한 책이라고 생각합니다.

책의 절대적인 가격은 높아보이지만 웬만한 책 두어권 보는 것보다 이 한 권의 책을 여러번 보는 것이 비용 대비 효과가 훨씬 좋을 것입니다.
이 책을 읽고 아낄 수 있는 많은 시간, 높아질 몸값을 생각하면 충분히 투자할 가치가 있는 책입니다.

http://www.acornpub.co.kr/book/toby-spring3-1-set

초난감 DAO

DAO의 클래스 하나에서 DB연결, SQL, CRUD, DB연결자원반납을 다 책임진다.

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
40
public class UserDao {

public Connection getConnection() {

try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}

try {
return DriverManager.getConnection("jdbc:mysql://localhost~~~", "id", "password");
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

public void crud1(User user) {

Connection c = getConnection();
PreparedStatement ps = null;

try {
ps = c.prepareStatement("update user set password = ? where id = ?");
ps.setString(1, user.getPassword());
ps.setString(2, user.getId());

ps.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (ps != null) {
try { ps.close(); } catch (SQLException e) {}
}
if (c != null) {
try { c.close(); } catch (SQLException e) {}
}
}
}
}

Imgur

클래스 다이어그램은 참 단순 깨끗하지만, DAO가 북치고 장구치고 다하는 모양새다. SRP에 따라 DAO 클래스도 하나의 책임만을 가지도록 바꿔보자.

먼저 DB 연결 부분부터 분리해보자!

템플릿 메서드 패턴을 이용한 DB 연결 부분 분리

아래와 같이 getConnection() 메서드를 abstract로 지정해서 DB 연결에 대한 구체적인 내용을 subclass에서 구현하게 한다. 이렇게 하면 DB 연결 내용을 UserDao에서 subclass로 분리해낼 수 있다.

crud1() 메서드는 템플릿 메서드로서 subclass에서 구현할 getConnection()를 호출해서 DB 연결을 가져와서 사용한다.

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
public abstract class UserDao {
// DB연결 부분은 subclass에서 결정
public abstract Connection getConnection();

public void crud1(User user) {

Connection c = getConnection();
PreparedStatement ps = null;

try {
ps = c.prepareStatement("update user set password = ? where id = ?");
ps.setString(1, user.getPassword());
ps.setString(2, user.getId());

ps.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (ps != null) {
try { ps.close(); } catch (SQLException e) {}
}
if (c != null) {
try { c.close(); } catch (SQLException e) {}
}
}
}
}

템플릿 메서드 패턴을 이용한 DB 연결 분리를 정리하면 다음과 같다.

  • getConnection()abstract로 해서 DB연결 부분을 subclass로 분리
  • UserDao는 더이상 구체적인 DB연결 부분을 신경쓰지 않는다.

클래스 다이어그램은 다음과 같다.

Imgur

그런데 템플릿 메서드 패턴처럼 상속을 이용한 방법만 있는 것은 아니다.

전략 패턴

다음과 같이 전략 패턴을 사용하면 DB 연결 부분을 subclass가 아니라 인터페이스(여기에서는 DataSource)를 통해 외부의 객체로 분리할 수도 있다.

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
40
41
42
43
interface DataSource {
Connection getConnection();
}

public class UserDao {

private DataSource dataSource;

public UserDao(DataSource dataSource) {
this.dataSource = dataSource;
}

public Connection getConnection() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

public void crud1(User user) {

Connection c = getConnection();
PreparedStatement ps = null;

try {
ps = c.prepareStatement("update user set password = ? where id = ?");
ps.setString(1, user.getPassword());
ps.setString(2, user.getId());

ps.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (ps != null) {
try { ps.close(); } catch (SQLException e) {}
}
if (c != null) {
try { c.close(); } catch (SQLException e) {}
}
}
}
}

전략 패턴을 통한 DB 연결 부분 분리를 정리하면,

  • DataSource 인터페이스를 통해 DB 연결의 구체적 내용을 외부로 분리해서 UserDao의 소스 코드 변경 없이도 dataSource를 다른 것으로 교체할 수 있다.
  • UserDao에 주입되는 DataSource 인터페이스의 구현체는 외부에서 주입받는다.

전략 패턴을 적용한 클래스 다이어그램은 다음과 같다.

Imgur

그렇다면 DB 연결의 구체적인 내용을 담고 있는 DataSource 구현체는 누가 주입해주나?

Factory Pattern

호출 관계로만 보면 UserDao를 사용하는 UserDaoTest가 구체적인 DataSource 구현체의 생성과 주입을 모두 담당할 수도 있다. 하지만 그런 것은 단순히 테스트를 실행하는 UserDaoTest의 역할이 아니다.

따라서 상황에 맞는 객체를 선별해서 생성할 수 있는 팩토리를 두어 위임하는 것이 좋을 것 같다.

아래와 같이 UserDaoFactory에서 구체적인 DataSource(여기에서는 ADataSource)를 UserDao에 주입한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DaoFactory {

public UserDao getUserDao() {
return new UserDao(new ADataSource());
}
}

public class UserDaoTest {

public static void main(String[] args) {
UserDao dao = new DaoFactory().getUserDao();

...
}
}

전략 패턴에 더하여 팩토리 패턴을 적용한 클래스 다이어그램은 다음과 같다.

Imgur

다시 정리하면,

DaoFactory가 필요한 객체 생성(new ADataSource(), new UserDao()) 및 관계 설정(userDao.setDataSource(new ADataSource()))을 담당한다.

객체 지향은 결국 협업이고, 협업의 바탕은 관계 설정인데, 그 관계 설정을 Factory가 담당하는 것이다. 이렇게 보니 Factory가 중요하다. Factory를 제대로 만들어보자.

ApplicationContext

Factory에서 생성하는 객체들을 Bean이라 하면, Bean을 생성하고 관계를 맺어주는 것은 BeanFactory의 책임이다.

BeanFactory에 Bean을 Singleton 방식으로 관리하는 능력, 의존 관계 탐색 기능 등을 추가해서 BeanFactory의 기능을 확장한 것이 바로 ApplicationContext다.

1
2
3
4
5
6
7
8
9
public class UserDaoTest {

public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
UserDao dao = context.getBean("userDao", UserDao.class);

...
}
}

ApplicationContext에서 Bean의 생성 및 관계 설정에 대한 정보를 application-context.xml로 분리할 수 있다. 이렇게 하면 ApplicationContext는 Bean을 물리적으로 생성하고 관계를 맺어주는 역할만을 담당하고, 그에 필요한 논리적인 정보는 외부(application-context.xml)로 분리할 수 있다.

ApplicationContext과 application-context.xml을 반영하면 클래스 다이어그램이 다음과 같이 변한다.

Imgur

이렇게 해서 초난감 DAO에서 시작해서 Spring의 근간을 이루는 ApplicationContext까지 살펴봤다.

정리해보면,

  1. DB 연결, SQL, CRUD, DB연결자원반납 기능을 모두 가진 초난감 DAO는 너무 많은 역할을 한 몸에 가지고 있다.
  2. 템플릿 메서드 패턴이나 전략 패턴으로 DB 연결을 외부로 분리할 수 있다.
  3. 역할에 따라 객체를 분리하면 분리된 객체 사이에 관계를 맺어줘야 객체 끼리 협력이 가능한데, 이런 역할을 ApplicationContext와 application-context.xml이 담당한다.

2장에서는 테스트에 대한 내용이 나오는데, 중요하지만 일단은 건너뛰고 3장에서 초난감 DAO에 여전히 남아 있는 DB 자원 반납 기능을 분리하는 방법을 알아본다.


크리에이티브 커먼즈 라이선스HomoEfficio가 작성한 이 저작물은(는) 크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.