본문 바로가기
Develop/SPRING

[오브젝트와 의존관계] - DAO의 확장

by 두린이홍이 2025. 5. 3.

이전는 데이터 엑세스 로직을 어떻게 만들 것인가와  DB 연결을 어떤 방법으로 할 것인가라는 두 개의 관심을 상하위 클래스로 분리하여 학습을 진행하였다. 이 두 개의 관심은 변화의 성격이 다르고, 그것은 변화의 이유와 시기, 주기 등이 다르다는 것을 의미한다.

이전 관심사의 분리에 대해서 기억이 나지 않는다면 아래 포스팅을 확인하여 한 번 더 어떻게 분리를 하였는지 확인할 수 있다.

 

오브젝트와 의존관계 - 관심사의 분리

지난 문서에서는 DAO, Data Access Obejct란 무엇인가. 그리고 과정은 어떻게 되는 가에 대해서 알아보았습니다.지난 문서를 다시 확인하고 싶으시다면 아래 링크를 클릭 하시면 DAO에 대해서 다시 같

lifeisstudy-hong.tistory.com

 

오늘은  DAO의 확장을 통해, 상속 관계가 아닌 완전 독립 클래스를 만들어보려고 한다.

아래는 SimpleConnectionMaker 클래스를 만들어 DB Connection에 대한 관심사를 분리하였다.

public class SimpleConnectionMaker {
	
	public Connection makeNewConnection() throws ClassNotFoundException, SQLException {
		Class.forName( "com.mysql.jdbc.Driver"); 
		Connection c = DriverManager.getConnection( "jdbc:mysql://localhost:3306/jspdb", "root", "1234"); 
		return c; 
	}
	
}

 

그리고 UserDAO에서 SimpleConnectionMaker를 활용해 데이터 엑세스 로직을 구현하였다.

public class UserDAO {

	private SimpleConnectionMaker simpleConnectionMaker;

	public UserDAO() {
		simpleConnectionMaker = new SimpleConnectionMaker();
	}
	
	public void add(User user) throws ClassNotFoundException, SQLException{
		Connection c = simpleConnectionMaker.makeNewConnection();
		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 = simpleConnectionMaker.makeNewConnection();
		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사나 D사에 공급했던 기능의 확장의 사용이 불가능해진 것을 확인할 수 있다. 이 말은 UserDAO의 코드가 SimpleConnectionMaker라는 특정 클래스에 종속되어 있기 때문에 상속을 했을 때 처럼 UserDAO 코드의 수정 없이 DB Connection 생성 기능을 변경할 방법이 사라진 것이다.

 

간단하게 두 가지 문제점에 대해 이야기하자면 아래와 같다.

첫 번째. SimpleConnectionMaker의 메소드의 문제이다. 

우리는 makeNewConnection()을 사용해 DB Connection을 가져오게 했는데, 만약 D사에서 만든 DB Connection 제공 클래스는 OpenConnection() 이라는 메소드 이름을 사용했다면 UserDAO 내에 있는 ADD나 GET 메소드의 Connection을 가져오는 코드를 일일이 변경해줘야 한다.

 

두번째. DB Connection을 제공하는 클래스가 어떤 것인지를 UserDAO가 구체적으로 알고있어야 한다는 점이다.

UserDAO에 SimpleConnectionMaker라는 클래스 타입의 인스턴스 변수까지 정의해놓고 있으니, N사에서 다른 클래스를 구현하면 어쩔 수 없이 UserDAO를 수정해야 한다. 

 

이런 문제의 근본적인 원인은 UserDAO가 바뀔 수 있는 정보, 즉 DB Connection을 가져오는 클래스에 대해 너무 많이 알고 있기 때문이다. 어떤 클래스가 쓰일지, 그 클래스에서 Connection을 가져오는 메소드는 이름이 뭔지까지 일일이 알고 있어야 한다.

따라서 UserDAO는 DB Connection을 가져오는 구체적인 방법에 종속되어 버린다.

 

위와 같은 문제점들을 해결하기 가장 좋은 해결책은 두 개의 클래스가 서로 긴밀하게 연결되어 있지 않도록 중간에 추상적인 느슨한 연결고리를 만들어주는 것이다.

 

 

인터페이스의 도입

*추상화란 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업이다. 
자바가 추상화를 위해 제공하는 가장 유용한 도구는 바로 인터페이스이다.

 

인터페이스는 자신이 구현한 클래스에 대한 구체적인 정보는 모두 감춰버린다. 결국 오브젝트를 만들기 위해서는 구체적인 클래스 하나를 선택해야겠지만 인터페이스로 추상화해놓은 최소한의 통로를 통해 접근하는 쪽에서는 오브젝트를 만들 때 사용할 클래스가 무엇인지는 몰라도된다.

 

아래는 인터페이스를 도입한 후 클래스의 관계를 표현한 것이다.

출처 토비의 스피링

인터페이스는 어떤 일을 하겠다는 기능만 정의해놓은 것이다. 따라서 인터페이스에는 어떻게 구현하겠다는 구현 방법은 나타나있지 않다. UserDAO가 인터페이스를 사용하게 한다면 인터페이스의 메소드를 통해 알 수 있는 기능에만 관심을 가지면 되지, 그 기능을 어떻게 구현했는지에는 관심을 둘 필요가 없다.

아래는 Connection에 대한 기능에 관심을 둔 ConnectionMaker Interface이다.

public interface ConnectionMaker {
	public Connection makeConnection() throws ClassNotFoundException, SQLException;
}

 

위 인터페이스에서 정의한 makeConnection() 메서드를 오바라이딩하여 기능을 어떻게 구현할 것인지는 아래와 같으 DConnectionMaker 클래스에 정의한다.

public class DConnectionMaker implements ConnectionMaker{

	@Override
	public Connection makeConnection() throws ClassNotFoundException, SQLException {
		Class.forName( "com.mysql.jdbc.Driver"); 
		Connection c = DriverManager.getConnection( "jdbc:mysql://localhost:3306/jspdb", "root", "1234"); 
		return c;
	}

}

 

이렇게 하면 UserDAO에서의 Connection은 내가 사용하고자 하는 구현체를 사용할 수 있다.

public class UserDAO {

	private ConnectionMaker connectionMaker;
		
	public UserDAO() {
		connectionMaker = new DConnectionMaker();
	}
	
	public void add(User user) throws ClassNotFoundException, SQLException{
		Connection c = connectionMaker.makeConnection();
		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 = connectionMaker.makeConnection();
		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;
	}
	
}

 

위 처럼 구현시 UserDAO의 add(), get() 메소드와 필드에는 ConnectionMaker라는 인터페이스와 인터페이스의 메소드인 makeConnection만 사용하도록 했다.

N사나 D사가 DB 접속용 클래스를 다시 만든다고 해도  UserDAO를 뜯어 고칠 일은 없지만 UserDAO를 보면 Dconnection이라는 클래스 이름이 보인다.

여전히 UserDAO에는 DConnection 클래스의 생성자를 호출해서 오브젝트를 생성하는 코드가 남아 있다.

 

UserDAO의 다른 모든 곳에서는 인터페이스를 이용하게 만들어서 DB Connection을 제공하는 클래스에 대한 구체적인 정보는 모두 제거가 가능했지만, 초기에 한 번 어떤 클래스의 오브젝트를 사용하지를 결정하는 생성자의 코드는 제거되지 않고 남아있다. 

 

그럼 UserDAO 변경 없이는 DB 커넥션 기능의 확장이 자유롭지 못한데, 기 이유는 UserDAO 안에 분리되지 않은 또 다른 관심사항 때문이다. 그래서 다음 포스팅에서는 남아있는 다른 관심사를 어떻게 분리할 지 알아보려고 한다.