본문 바로가기

개발 독서/토비의 스프링3

[토비의 스프링3] 1. 오브젝트와 의존관계(1) - DAO 분리

728x90
반응형

스프링이 관심 갖는 대상인 오브젝트의 설계와 구현, 동작원리에 집중하여 아래 예제를 통해 스프링이 무엇인지 이해해보자.

1. 간단한 DAO로 문제점을 파악해보자


❓ DAO (Data Access Object)
DB를 사용해 데이터를 조회, 조작하는 기능을 전담하도록 만든 오브젝트.

사용자 정보를 Jdbc API를 통해 DB에 저장하고 조회할 수 있는 간단한 DAO를 만들어보자.

1-1. User Class 생성

public class User { String id; String name; String password; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
  • id, name, password 세 개의 프로퍼티를 가진 User 클래스이다.
  • 자바빈 규약을 따르는 오브젝트를 생성하였다.

 자바빈 : 아래의 조건을 충족하는 오브젝트를 가리킨다.

  1. 디폴트 생성자가 있어야 한다. (툴이나 프레임워크에서 리플랙션을 이용해 오브젝트를 생성하기 때문에 필요하다) 
  2. 프로퍼티setter, getter 접근메서드를 통해 수정, 조회가 가능해야 한다.
    - 프로퍼티는 자바빈이 노출하는 이름을 가진 속성이다.

1-2. UserDao 생성

  • 사용자 정보를 관리하는 UserDao를 만든다.
  • 사용자 생성(add), 사용자 정보 조회(get)
public class UserDao { public void add(User user) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book"); PreparedStatement ps = c.prepareStatement("insert into user(id, name, password) values(?,?,?)"); ps.setString(1, user.getId()); ps.setString(2, user.getName()); ps.setString(3, user.getPassword()); ps.executeUpdate(); ps.close(); c.close(); } // get() 생략 }

작업 순서

  1. DB Connection 가져오기
  2. SQL 담은 Statement(Prepared Statement) 만들기
  3. Statement 실행
  4. 조회의 경우 실행 결과를 ResultSet 으로 받아 오브젝트에 옮긴다 (User)
  5. 작업 후 Connection, Statement, ResultSet 닫기

DAO를 테스트 해보려면 어떻게 해야할까?

  1. DAO 기능을 사용하는 웹 애플리케이션을 만들고 웹 브라우저를 통해 DAO 기능을 사용한다. → UserDao만 확인하고 싶은데 배보다 배꼽이 더 크다. 😖
  2. main()을 이용한 DAO 테스트 코드를 작성한다. : 만든 DAO 내부에 main 메서드를 만들고 User 오브젝트 생성, 프로퍼티 값 넣고 add() 메서드 이용해 DB를 등록한다. 각 단계별로 콘솔 메시지도 출력해볼 수 있다.

but! 2번 방법에도 문제점이 많다. 왤까?
잘 동작하는 코드를 구지 개선하는 이유가 뭘까? 장점은? 스프링을 사용하는 개발에서 무슨 차이가 있을까? 아래 과정을 통해 문제점을 해결하며 리팩토링 해보자.

2. 관심사의 분리

왜 해야할까?

한 가지 관심에 대해 문제가 일어나지만 그에 따른 작업은 한 곳에 집중되지 않는 경우가 많다. DB 접속용 암호를 바꾸려고 DAO 클래스 수백 개를 모두 수정한다면? 트랜잭션 기술을 교체한다고 비즈니스 로직 코드 구조를 모두 변경해야 한다면? 등의 상황을 대비하기 위함이다.

관심이 같은 것 끼리 모으고, 관심이 다른 것은 따로 떨어져 있게 해야한다.

2-1. 커넥션 만들기의 추출

UserDao의 관심사항

  1. DB 연결을 위한 Connection 오브젝트를 가져온다.
  2. 사용자 등록을 위해 DB에 보낼 SQL 문장을 담을 Statement를 만들고 실행한다.
  3. 리소스를 시스템에 반납한다.

중복 코드의 메서드 추출

public void add(User user) throws ClassNotFoundException, SQLException { Connection c = getConnection(); // .... } public void get(String id) throws ClassNotFoundException, SQLException { Connection c = getConnection(); // .... } private Connection getConnection() throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook", "spring", "book"); return c; }
  • getConnection() 로 1번 관심사항을 분리시켰다. add(), get() 에서 중복되는 코드는 분리된 메서드를 호출하게 된다.

2-2. 커넥션 만들기의 독립

문제 상황 도출

  • N사와 D사에게 UserDao 를 납품해야 한다.
  • 각 사는 다른 종류의 DB를 사용하고 있다.
  • DB 커넥션을 가져오는데 있어 독자적으로 만든 방법을 적용하고 싶어한다.
  • 구매 이후에도 커넥션 방법이 변경될 수 있다.

→ 어떻게하면 UserDao 소스코드를 N,D사에 제공해주지 않고 고객 스스로 원하는 DB 커넥션 생성 방식을 적용해가면서 UserDao를 사용하게 할 수 있을까?

상속을 통한 확장

getConnection() 내 코드를 제거하고 추상메서드로 만든다. 추상 메서드라 add(), get() 코드 내 getConnection()는 그대로 있게 된다. NUserDao, DUserDao 서브클래스를 만들고 UserDao 클래스를 상속해서 getConnection()을 각 사별로 원하는 방식대로 구현한다.

→ UserDao 소스코드를 제공해서 수정해 쓰지 않아도 원하는 방식으로 확장하여 UserDao 기능과 함께 사용할 수 있다.

관심사가 클래스 레벨로 분리되었다.

  • UserDao : 어떻게 데이터를 등록, 가져올 것인가(SQL 작성, 파라미터 바인딩, 쿼리 실행, 검색정보 전달)
  • NUserDao, DUserDao : DB 연결 방법은 어떻게 할 것인가

수정된 로직에는 두 가지 디자인 패턴이 적용되었다고 볼 수 있다.

  1. 템플릿 메소드 패턴
  2. 팩토리 메소드 패턴


템플릿 메소드 패턴

  • 변하지 않는 기능은 슈퍼클래스에 만들어두고 자주 변경,확장되는 기능은 서브클래스로 확장하여 만드는 것이다.
  • 슈퍼클래스에 미리 추상 메소드/오버라이드 가능 메소드(훅메소드)를 정의해두고 이를 담고있는 기본 템플릿 메소드를 만든다.
  • 훅메소드 : 슈퍼클래스에서 디폴트 기능을 정의해두거나, 비웠다가 서브클래스에서 선택적으로 오버라이드할 수 있도록 만들어둔 메소드 (선택적으로 오버라이드 가능)
  • 추상 메소드 : 슈퍼클래스에 정의만 해두고 상속 시 반드시 구현을 강제한 메소드 (반드시 구현)
public static class Super { public void templateMethod() { // 기본 골격을 담고있는 메소드 // 기본 알고리즘 코드 hookMethod(); abstractMethod(); } protected void hookMethod() {} // 선택적으로 오버라이드 가능 public abstract void abstractMethod(); // 서브클래스에서 반드시 구현 } public class Sub1 extends Super { // Super 클래스의 기능을 확장한다. protected void hookMethod() { ... } public void abstractMethod() { ... } }


팩토리 메소드 패턴

  • 팩토리 메소드 : 서브클래스에서 오브젝트 생성 방법과 클래스를 결정할 수 있도록 미리 정의해둔 메소드
  • 팩토리 메소드 패턴 : 위 방식을 통해 오브젝트 생성 방법을 나머지 로직, 즉 슈퍼클래스의 기본 코드에서 독립시키는 방법

 뭐지..? 두 패턴이 뭐가 다른거지?

→ 상속을 통해 독립적으로 변경하고 기능을 확장한다는 의미는 같다. 템플릿 메소드 패턴은 기본 규격을 갖춘 템플릿을 만들고 그 중간에 들어가는 로직을 갈아끼우는 것이다. 팩토리 메소드 패턴은 서브 클래스에서 오브젝트를 생성 방법을 결정하고 그 로직을 분리하여 슈퍼 클래스의 기본 코드로 부터 독립시키는 것이다.

상속을 했을 때의 한계점

  • UserDao 가 다른 목적을 위해 상속을 사용하고 있다면?
    • 다중 상속이 불가하다.
  • 상속을 통한 상하위 클래스 관계를 생각보다 밀접하다.
    • 서브클래스는 슈퍼클래스의 기능을 직접 사용할 수 있다. 슈퍼클래스 내부 변경이 있을 때 모든 서브클래스를 함께, 수정/개발 해야할 수도 있다.
  • 다른 DAO 클래스들이 만들어진다면?
    • getConnection() 구현 코드가 매 DAO 클래스마다 중복될 것이다.

 

✔ To do

 

자바 리플렉션 (Reflection) - 소개

오늘은 자바의 리플렉션 기능에 대해 정리해보려고 한다. 정리는 두차례로 진행이 될 예정이고, 오늘은 리플렉션에 대해서 간단히 알아보려고 한다. 리플렉션은 구체적인 클래스 타입을 알지

lkhlkh23.tistory.com

  • abstract의 public, protected 붙는 차이
728x90
반응형