상세 컨텐츠

본문 제목

[1.3] DAO의 확장

토비의스프링

by kwanghyup 2020. 10. 27. 16:14

본문

1. 클래스의 분리 

 

DB커넥션 관련 부분을 상속 관계도 아닌 완전히 독립적인 클래스로 만들어보자. 

그렇게 만든 클래스를 UserDao가 이용하게 하면 된다. 

UserDao는 이제 더 이상 추상클래이스일 필요가 없다.

 

SimpleConnectionMaker 클래스를 만들고 DB생성기능을 그 안에 넣는다. 

 

독립시킨 DB연결 기능인 SimpleConnectionMaker

package springbook.user.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class SimpleConnectionMaker {
	
	public Connection makeNewConnection() throws ClassNotFoundException, SQLException {
		Class.forName("com.mysql.cj.jdbc.Driver");
		String url = "jdbc:mysql://localhost:3306/tobiespring?characterEncoding=utf8&serverTimezone=UTC";
		Connection c = DriverManager.getConnection(url,"root","12345678");
		return c;
	}
	
}

 

 기존에 만들어 둔 DUserDao와 NUserDao를 삭제하자. 

package springbook.user.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import springbook.user.domain.User;

// 더 이상 추상클래스일 필요가 없다. 
public class UserDao {
	
	// 기존의 추상메서드를 삭제한다. 
	
	// SimpleConnectionMaker 필드 변수로 선언했다. 
	private SimpleConnectionMaker simpleConnectionMaker;
	
	// 상태를 관리하는 것이 아니므로 생성자를 통해 인스턴스 변수로 저장해두고 메소드에서 사용하게 한다. 
	public UserDao() {
		simpleConnectionMaker = new SimpleConnectionMaker();
	}
	
	public void add(User user) throws ClassNotFoundException, SQLException {
		// 변경된 부분  
		Connection c = simpleConnectionMaker.makeNewConnection(); 
		
		String sql = "insert into users values(?,?,?)";
		PreparedStatement ps = c.prepareStatement(sql);
		ps.setString(1, user.getId());
		ps.setString(2, user.getName());
		ps.setString(3, user.getPassword());
		
		ps.executeUpdate();
		
		ps.close();
		c.close();
	}
	
	public User get(String id) throws ClassNotFoundException, SQLException {
		// 변경된 부분
		Connection c= simpleConnectionMaker.makeNewConnection();
		
		String sql = "select * from users where id = ?";
		PreparedStatement ps = c.prepareStatement(sql);
		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;
	}	
	
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		
		// 다시 UserDao 인스턴스를 생성하자. 
		UserDao dao = new UserDao();
		
		User user = new User();
		user.setId("whiteship");
		user.setName("백기선");
		user.setPassword("married");
		
		dao.add(user);
		
		System.out.println(user.getId() + " 등록성공");
	}
	
}

 

테스트를 하기전에 DB를 초기화하자. 

 

코드를 분리했지만 UserDao 소스코드를 공개하지 않고서는 

 고객사별로 원하는 DB커넥션을 만들어서 사용하는 것이 불가능해졌다. 

다음의 코드를 수정하지 않고서는 DB연결 방법을 바꿀 수 없기때문이다.

public UserDao() {
		simpleConnectionMaker = new SimpleConnectionMaker();
}

 

클래스를 분리한 경우에 자유로운  확장이 가능하게 하려면 두 가지 문제를 해결해야한다.

 

현재 메소드의 이름이 makeNewConnection()을 사용하여 DB커넥션을 가져오게 했다.

그런데 고객사에서 만든 DB 커넥션 제공 클래스는 openConnection이라는 메소드의 이름을 사용한다면

UserDao내에 있는 add(), get()메소드의 커넥션을 가져오는 코드를 모두 수정해주어야 한다. 

 

두 번째 문제는 DB커넥션을 제공하는 클래스가 어떤 것인지를

UserDao가 구체적으로 알고 있어야 한다는 점이다. 

 

이런 문제의 근본적 원인은  UserDao가 DB커넥션을 가져오는

구체적인방법에 종속되어 있기 때문이다..

즉, UserDao가  바뀔 수 있는 정보(DB커넥션을 가져오는 클래스)에 대해

너무 많이 알고 있다는 것이다. 

 

2. 인터페이스의 도입 

 

클래스를 분리하면서도 위의 문제를 해결해보자. 

 

먼저 ConnectionMaker 인터페이스를 정의하자. 

DB커넥션을 가져오는 이름을 makeConnection()이라고 하자.

package springbook.user.dao;

import java.sql.Connection;
import java.sql.SQLException;

public interface ConnectionMaker {

	public Connection makeConnection() throws ClassNotFoundException, SQLException; 
	
}

이렇게 하면 ConnectionMaker타입의 오브젝트라면

어떤 클래스로 만들어졌든지 상관없이 makeConnection()메소드를 호출하기만 하면

 Connection타입의 오브젝트를 만들어서 돌려줄 것이라고 기대할 수 있다.

 

고객사에서는 UserDao클래스와 함께 ConnectionMaker인터페이스도 전달받는다. 

그리고 나서 고객사의 개발자는 ConnectionMaker인터페이스를 구현한 클래스를 만들고 

자신들의 DB연결 기술을 이용해 DB커넥션을 가져오도록 메서드를 작성하면된다. 

 

ConnectionMaker구현 클래스 

package springbook.user.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DConnectionMaker implements ConnectionMaker{

	@Override
	public Connection makeConnection() throws ClassNotFoundException, SQLException {
		
		System.out.println("D사의 독자적 방법으로 Connection 생성");
		
		Class.forName("com.mysql.cj.jdbc.Driver");
		String url = "jdbc:mysql://localhost:3306/tobiespring?characterEncoding=utf8&serverTimezone=UTC";
		Connection c = DriverManager.getConnection(url,"root","12345678");
		return c;
	}
}

 

이제 UserDao가 ConnectionMaker 인터페이스를 이용하여 

DB커넥션을 가져오게 한다.

package springbook.user.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import springbook.user.domain.User;

 
public class UserDao {
	
	// connectionMaker 필드 변수로 선언했다. 
	private ConnectionMaker connectionMaker;
	
	// 그런데 UserDao가 고객사에서 만든 클래스의 이름을 알아야 하는 문제가 생긴다.  
	public UserDao() {
		connectionMaker = new DConnectionMaker();
	}
	
	public void add(User user) throws ClassNotFoundException, SQLException {
		// 인터페이스의 메소드를 호출한다 .  
		Connection c = connectionMaker.makeConnection(); 
		
		String sql = "insert into users values(?,?,?)";
		PreparedStatement ps = c.prepareStatement(sql);
		ps.setString(1, user.getId());
		ps.setString(2, user.getName());
		ps.setString(3, user.getPassword());
		
		ps.executeUpdate();
		
		ps.close();
		c.close();
	}
	
	public User get(String id) throws ClassNotFoundException, SQLException {
		// 변경된 부분
		Connection c = connectionMaker.makeConnection();
		
		String sql = "select * from users where id = ?";
		PreparedStatement ps = c.prepareStatement(sql);
		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;
	}	
	
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		 
		UserDao dao = new UserDao();
		
		User user = new User();
		user.setId("whiteship");
		user.setName("백기선");
		user.setPassword("married");
		
		dao.add(user);
		
		System.out.println(user.getId() + " 등록성공");
	}
	
}

 

DB를 초기화하고 테스트를 하자. 

 

이제는 고객사마다 DB커넥션 클래스를 다시 만든다고하여도

UserDao add(), get()의 코드를 수정하지 않아도 된다. 

 

그런데 여전히 UserDao는 고객사가 만든 DB커넥션의 구현 클래스 이름을 알아야한다.

이래선 고객에게  자유로운 DB 커넥션 확장 기능을 가진 UserDao를 제공할 수 없다. 

 

3. 관계설정 책임분리 

 

여전히 UserDao가 어떤 ConnectionMaker 구현 클래스를 사용할지 결정하는 코드가 남아 있다.

connectionMaker = new DConnectionMaker();

 

UserDao 생성자를 다음과 같이 수정하자. 

	public UserDao(ConnectionMaker connectionMaker) {
		this.connectionMaker = connectionMaker;
	}

이렇게하면 UserDao가 ConnectionMaker 구현 클래스에 대해 알 필요가 없다. 

생성자를 통해 클라이언트 측에서 ConnectionMaker구현 클래스를 전달받기 때문이다. 

 


이제 UserDao를 사용하는 UserDaoTest클래스를 만들고 

이전에 main()메소드에 있던 코드를 옮기자. 

 

이 UserDaoTest가 클라이언트가 되면

여기서 어떤 ConnectionMaker 구현클래스를 사용할 것인지 결정하게 된다. 

 

테스트를 하기전에 DB를 초기화하자 

package springbook.user.dao;

import java.sql.SQLException;

import springbook.user.domain.User;

public class UserDaoTest {
	
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		 
		// UserDao가 사용할 ConnectionMaker구현 클래스를 결정하고 오브젝트를 만든다. 
		ConnectionMaker connectionMaker = new DConnectionMaker(); 
		
		//클라이언서 UserDao 사용시에 위에서 생성한 ConnectionMaker타입의 오브젝트를 제공한다. 
		UserDao dao = new UserDao(connectionMaker);
		
		User user = new User();
		user.setId("whiteship");
		user.setName("백기선");
		user.setPassword("married");
		
		dao.add(user);
		
		System.out.println(user.getId() + " 등록성공");
	}
	
}

 

'토비의스프링' 카테고리의 다른 글

[1.6] 싱글톤 레지스트리와 오브젝트 스코프  (0) 2020.10.27
[1.5]Spring IoC  (0) 2020.10.27
[1.4] 제어의 역전  (0) 2020.10.27
[1.2] DAO의 분리  (0) 2020.07.30
[1.1] 초난감DAO  (0) 2020.07.30

관련글 더보기

댓글 영역