지난 문서에서는 DAO, Data Access Obejct란 무엇인가. 그리고 과정은 어떻게 되는 가에 대해서 알아보았습니다.
지난 문서를 다시 확인하고 싶으시다면 아래 링크를 클릭 하시면 DAO에 대해서 다시 같이 학습할 수 있습니다.
오브젝트와 의존관계 - DAO
개발 1년차. 요즘 기본기와 기초가 많이 부족한 거 같다는 생각을 많이한다. 누구는 그렇게까지 깊게 공부하지 않아도 GPT가 다 해준다라는 속 편한 말을 하는 직장 동료도 있지만 꾸준히 하다보
lifeisstudy-hong.tistory.com
오늘은 지난 학습 때 들었던 의문점인 데이터베이스가 변경되게 된다면 그 수 많은 기능에 대한 Connection을 전부 변경해줘야 하나? 라는 의문에 대해 관심사를 분리함으로써, 데이터베이스 연결과 기능 수행 부분을 분리하는 것에 대해 학습하였습니다.
관심사의 분리
UserDAO에서 구현된 메소드를 살펴보면, 적어도 세 가지의 관심사항을 발견할 수 있다.
public class UserDAO {
// 사용자 추가
public void add(User user) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
// 연결문자열 , DB_ID, DB_PW
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/jspdb", "root", "1234" );
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());
// update 의 반환자는 int 형으로 0 또는 1이다.
ps.executeUpdate();
ps.close();
c.close();
}
// 사용자 조회
public User get(String id) throws ClassNotFoundException, SQLException{
Class.forName("com.mysql.jdbc.Driver");
// 연결문자열 , DB_ID, DB_PW
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/jspdb", "root", "1234" );
PreparedStatement ps = c.prepareStatement("select * from users where id = ?" );
ps.setString(1, id);
ResultSet rs = ps.executeQuery();
rs.next();
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
rs.close();
ps.close();
c.close();
return user;
}
}
- 첫 번째는 데이터베이스와의 연결을 위한 Connection 영역
- 두 번째는 데이터베이스에 어떤 쿼리를 전달할 지에 대한 Statement 영역
- 세 번째는 작업이 끝난 이후에 리소스를 처리하기 위한 Close 영역
위 세 가지의 관심사항이 반복된다는 것을 알 수 있었다.
중복 코드의 메소드 추출 필요
계속 반복되는 코드들을 추출하고 분리함으로써 미래의 변화에도 쉽게 대응할 수 있게 할 수 있다.
반복되는 코드는 아래와 같이 추출하면 된다.
UserDAO
private Connection getConnection() throws ClassNotFoundException, SQLException{
Class.forName("com.mysql.jdbc.Driver");
// 연결문자열 , DB_ID, DB_PW
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/jspdb", "root", "1234" );
return c;
}
// 사용자 추가
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
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());
// update 의 반환자는 int 형으로 0 또는 1이다.
ps.executeUpdate();
ps.close();
c.close();
}
위와 같이 Connection 부분을 공통의 관심사항으로 분리하여 사용하면, 데이터베이스가 변하게 되거나, 계정이 변경이 되더라도 변화에 쉽게 대응할 수 있게된다.
리펙토링 ( Refactoring )
위와 같이 관심사항을 분리하는 작업을 리펙토링이라고 한다. 또한 위에서 사용한 getConnection()이라고 하는 공통의 기능을 담당하는 메소드로 중복된 코드를 뽑아내는 것을 리팩토링에서는 메소드 추출(Extract Method) 기법이라고 한다.
이제 여기서 바로 다음 의문이 들 수 있다.
예를 들어, 내가 제공하고자 하는 서비스가 독립적인 하나의 솔루션이라고 하면 이해가 가능하지만 내 시스템을 다른 사람이 사용을 하는데 데이터베이스가 다르다면...? 어떻게 하지?
결론은 누가 접근을 하더라도 독립적으로 사용할 수 있게 확장성을 주어야 한다. 즉, 한 단계 더 나아가 DB 커넥션을 완전히 독립적으로 사용할 수 있다.
만약 N사와 D사가 별도의 데이터베이스를 사용하고자 한다면, 위와 같이 하나의 커넥션을 사용하는 것엔 한계가 있을 것이다. 위 문제를 해결하기 위해서 상속이라는 것을 사용한다.
상속을 위한 확장
위에서 만든 UserDAO에서 메소드의 구현 코드를 제거하고 getConnection()을 추상메소드로 만들어 놓는다.
추상메서드이기 때문에 메소드의 코드는 없지만 메소드 그 자체는 존재하게 된다.

출처 : 토비의 스프링
UserDAO
public abstract class UserDAO {
public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}
NuserDAO
public class NuserDAO extends UserDAO{
@Override
public Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
// 연결문자열 , DB_ID, DB_PW
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/jspdb", "root", "1234" );
return c;
}
// 사용자 추가
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection();
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());
// update 의 반환자는 int 형으로 0 또는 1이다.
ps.executeUpdate();
ps.close();
c.close();
}
// 사용자 조회
public User get(String id) throws ClassNotFoundException, SQLException{
Connection c = getConnection();
PreparedStatement ps = c.prepareStatement("select * from users where id = ?" );
ps.setString(1, id);
ResultSet rs = ps.executeQuery();
rs.next();
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
rs.close();
ps.close();
c.close();
return user;
}
}
일단 N사를 예를 들어서 NuserDAO를 만들어놓았고, DuserDAO도 별도로 존재한다고 생각하면 된다.
수정한 코드를 보면, 두 가지의 관심사항이 존재한다.
- 어떻게 데이터를 등록하고 가져올 것인가라는 관심을 가지는 UserDAO
- DB 연결 방법은 어떻게 할 것인가라는 관심을 가지는 NuserDAO와 DuserDAO
두 개의 DAO는 각 각 독립적으로 분리되면서 변경 작업은 한층 용이해지게 된다.
어느 환경이든 기본적인 프레임을 가진 상태로 상속을 받으며 변화에 대응이 가능하게 된다.
지금까지 데이터 엑세스 로직을 어떻게 만들 것인가와 DB 연결을 어떤 방법으로 할 것인가라는 두 개의 관심을 상하위 클래스로 분리시켰다.
이 두 개의 관심은 변화의 성격이 다르고, 그것은 변화의 이유와 시기 그리고 주기 등이 다르다는 뜻이다.
'Programming > Spring' 카테고리의 다른 글
[오브젝트와 의존관계] - 관계 설정 책임의 분리 (0) | 2025.05.03 |
---|---|
[오브젝트와 의존관계] - DAO의 확장 (1) | 2025.05.03 |
[오브젝트와의존관계] - DAO (0) | 2025.05.03 |
[Spring] 파일 업로드 (0) | 2024.05.26 |
[Spring] AOP, 관점 지향 프로그래밍 (0) | 2024.05.26 |