TIL
day26 TIL
dalooong
2023. 7. 9. 02:33
Spring - UserDao 템플릿 메소드 적용하기
💡 템플릿 메소드 패턴(Template Method Pattern) 슈퍼 클래스에 기본적인 로직의 흐름(커넥션 가져오기, SQL 생성, 실행, 반환)을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 메소드로 만든 뒤 서브 클래스에서 이런 메소드를 필요에 맞게 구현해서 사용하도록 하는 방법을 디자인 패턴에서 템플릿 메소드 패턴이라고 한다.
1. 커넥션 메서드의 분리와 각각의 구현
- UserDaoUserDao의 구현체는 NUserDao와 DUserDao 클래스로 구현한다.
- UserDao 클래스를 추상 클래스로, Connection 메소드를 추상 메서드로 변경한다.
public **abstract** class UserDao {
public **abstract** Connection getConnection() throws ClassNotFoundException, SQLException;
- DUserDao
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DUserDao extends UserDao {
@Override
public Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("", "", "");
return connection;
}
}
- NUserDao
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;
import static java.lang.System.getenv;
public class NUserDao extends UserDao {
@Override
public Connection getConnection() throws ClassNotFoundException, SQLException {
Map<String, String> env = getenv();
String dbHost = env.get("DB_HOST");
String dbUser = env.get("DB_USER");
String dbPassword = env.get("DB_PASSWORD");
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection(dbHost, dbUser, dbPassword);
return connection;
}
}
2. 커넥션 클래스의 분리
관심사가 다르고 변화의 성격이 다른 DB 연결과 DB 사용 코드를 독립된 클래스로 분리한다.
→ 커넥션 클래스를 분리함으로써 코드의 모듈화, 단일 책임 원칙 준수, 코드 재사용성, 확장성, 유연성, 테스트 용이성을 개선할 수 있다.
- SimpleConnectionMaker 클래스
public class SimpleConnectionMaker {
public Connection makeNewConnection() throws ClassNotFoundException, SQLException {
Map<String, String> env = getenv();
String dbHost = env.get("DB_HOST");
String dbUser = env.get("DB_USER");
String dbPassword = env.get("DB_PASSWORD");
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(
dbHost, dbUser, dbPassword
);
return conn;
}
}
- 독립된 SimpleConnectionMaker를 사용하게 만든 UserDao
- public class UserDao { SimpleConnectionMaker simpleConnectionMaker = new SimpleConnectionMaker(); public void add(User user) throws ClassNotFoundException, SQLException { Connection conn = simpleConnectionMaker.makeNewConnection(); // .. 생략 } public User get(String id) throws ClassNotFoundException, SQLException { Connection conn = simpleConnectionMaker.makeNewConnection(); // .. 생략 } }
3. 커넥션 인터페이스
클래스를 독립해서 관심사를 분리하는 것은 좋았지만 UserDao에서 SimpleConnectionMaker에 강하게 결합되어 있어 DB Connection 변경이 불가하다.
ConnectionMaker 인터페이스를 사용해 다양한 구현체가 UserDao와 결합할 수 있게 한다.
- ConnectionMaker 인터페이스
public **interface** ConnectionMaker {
public Connection getConnection() throws SQLException, ClassNotFoundException;
}
- 인터페이스 구현체 1 - DConnectionMaker
public class DConnectionMaker implements ConnectionMaker{
@Override
public Connection getConnection() throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(
"","",""
);
return conn;
}
}
- 인터페이스 구현체 2 - NConnectionMaker
public class NConnectionMaker implements ConnectionMaker{
@Override
public Connection getConnection() throws SQLException, ClassNotFoundException {
Map<String, String> env = getenv();
String dbHost = env.get("DB_HOST");
String dbUser = env.get("DB_USER");
String dbPassword = env.get("DB_PASSWORD");
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(
dbHost, dbUser, dbPassword
);
return conn;
}
}
- UserDao
public class UserDao {
ConnectionMaker connectionMaker; // 인터페이스 사용
public UserDao() { // 특정 구현체에 의존
****this.connectionMaker = new NCconnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.getConnection();
// 생략 ..
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.getConnection();
// 생략 ...
}
public static void main(String[] args) throws SQLException, ClassNotFoundException {
UserDao userDao = new UserDao();
User selectedUser = userDao.get("4");
System.out.println(selectedUser.getId());
System.out.println(selectedUser.getName());
System.out.println(selectedUser.getPassword());
}
}
UserDao 내에서 ConnectionMaker를 사용할 때 인터페이스를 사용해서 어떤 인터페이스 구현체와 결합되어도 상관없지만 구현체가 바뀔 때는 UserDao 생성자의 코드가 바뀌어야 한다.
4. 관계 설정 책임의 분리
UserDao 내에 NConnectionMaker 구현체를 사용한다고 결정하는 생성자 내의 코드를 삭제하고, 외부에서 만들어 사용하기 위해 생성자의 파라미터로 받아 주입한다.
public class UserDao {
ConnectionMaker connectionMaker; // 인터페이스 사용
public UserDao(ConnectionMaker connectionMaker) { // 생성자 주입
~~this.connectionMaker = new NCconnectionMaker();~~
// UserDao는 어떤 구현체와 결합할지 모르게 구현
this.connectionMaker = connectionMaker;
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.getConnection();
// 생략 ..
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.getConnection();
// 생략 ...
}
public static void main(String[] args) throws SQLException, ClassNotFoundException {
// UserDao를 생성할 때 외부에서 ConnectionMaker의 구현체 주입
UserDao userDao = new UserDao(new **NConnectionMaker**());
User selectedUser = userDao.get("4");
System.out.println(selectedUser.getId());
System.out.println(selectedUser.getName());
System.out.println(selectedUser.getPassword());
}
}
5. 런타임 오브젝트 의존관계 책임 분리
- UserDao 클래스 내 런타임 코드 삭제
public class UserDao {
ConnectionMaker connectionMaker; // 인터페이스 사용
public UserDao(ConnectionMaker connectionMaker) { // 생성자 주입
this.connectionMaker = connectionMaker;
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.getConnection();
// 생략 ..
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.getConnection();
// 생략 ...
}
- UserDao 내 런타임 코드 → UserDaoTest Class로 이동
public class **UserDaoTest** {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
ConnectionMaker connection = new NConnectionMaker();
UserDao userDao = new UserDao(connection);
User selectedUser = userDao.get("4");
System.out.println(selectedUser.getId());
System.out.println(selectedUser.getName());
System.out.println(selectedUser.getPassword());
}
}