UserDao를 N사, D사 연결에 따라 다르게 구현해야 할 수도 있다. 따라서 이 경우를 대비해 UserDao를 abstact class로 만들고, 추상 클래스를 extends한 구현체들을 생성한다. 이해를 돕기 위해 기존의 getConnection을 NUserDao로, 로컬에서의 getConnection을 DUserDao로 구현한다.
이를 템플릿 메소드 패턴이라고 한다. 템플릿 메소드 패턴은 abstract class를 이용해 바뀌는 메소드만 추상 메소드로 정의 해놓고 바뀌는 부분을 추상클래스의 구현체로 사용하는 패턴이다.
**// UserDao**
package com.example.tobyspring3.dao;
import com.example.tobyspring3.domain.User;
import java.sql.*;
public abstract class UserDao {
public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}
**// NUserDao**
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 conn = DriverManager.getConnection(
dbHost, dbUser, dbPassword
);
return conn;
}
}
**// DUserDao**
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;
import static java.lang.System.getenv;
public class DUserDao extends UserDao{
@Override
public Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("", "", "");
return conn;
}
}
예를 들어 makeConnection 같은 경우 D와 N이라는 구현체를 가지고 makeQuert 같은 경우 D와 K의 구현체를 가질 때, 자바는 다중 상속을 지원하지 않아서 makeQuery는 추상화한 채로 확장이 불가능하다. D의 입장에서는 makeConnection과 makeQuery라는 충돌이 생기기 때문이다. abstrat는 무조건 구현해 줘야 하기 때문에 이런 확장성에 있어 추상 클래스로 기능을 분리하는 것은 확장성에 있어서 비효율적이다. 따라서 기능을 가진 새로운 class를 만들어서 객체로 접근하는 분리 방식을 더 추구한다.
**// simple connection maker**
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;
}
}
**// UserDao**
package com.example.tobyspring3.dao;
import com.example.tobyspring3.domain.User;
import java.sql.*;
import static java.lang.System.getenv;
public class UserDao {
SimpleConnectionMaker connectionMaker = new SimpleConnectionMaker();
public void add(User user) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.makeNewConnection();
PreparedStatement pstmt = conn.prepareStatement("insert into User(id, name, password) values(?, ?, ?)");
pstmt.setString(1, user.getId());
pstmt.setString(2, user.getName());
pstmt.setString(3, user.getPassword());
pstmt.executeUpdate();
pstmt.close();
conn.close();
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.makeNewConnection();
PreparedStatement pstmt = conn.prepareStatement("select id, name, password from User where id = ?");
pstmt.setString(1, id);
ResultSet rs = pstmt.executeQuery(); // 내가 요청한 결과값이 rs에 담아옴
rs.next();
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
// 안 닫아 주면 계속 연결돼 있음 -> 연결만 늘어나서 서비스 장애가 생길 수 있음
pstmt.close();
conn.close();
rs.close();
return user;
}
public static void main(String[] args) throws SQLException, ClassNotFoundException {
UserDao userDao = new UserDao();
User user = new User();
user.setId("2");
user.setName("huisu");
user.setPassword("2459810");
User selectuser = userDao.get("2");
System.out.println(selectuser.getId());;
System.out.println(selectuser.getName());
System.out.println(selectuser.getPassword());
}
}
하지만 이는 SimpleConnectionMaker 객체와 다시 강결합 상태이며 의존성이 높다. DB 연결인 SimpleConnectionMaker 코드를 변경했을 때 UserDao의 코드 수정이 필수적으로 된다. SimpleConnectionMaker라는 클래스에 종속되어 있기 때문이다. 이 문제를 해결하기 위해 Interface로 구현한다. 먼저 Interface를 만든다.
package com.example.tobyspring3.dao;
import java.sql.Connection;
public interface ConnectionMaker {
Connection makeConnection();
}
해당 Interface에 대해 구현체를 만든다.
package com.example.tobyspring3.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;
import static java.lang.System.getenv;
public class DConnectionMaker implements ConnectionMaker{
@Override
public Connection makeConnection() 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;
}
}
Interface를 사용한 UserDao 실행 코드는 다음과 같다.
package com.example.tobyspring3.dao;
import com.example.tobyspring3.domain.User;
import java.sql.*;
import static java.lang.System.getenv;
public class UserDao {
ConnectionMaker connectionMaker;
public UserDao() {
this.connectionMaker = new DConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.makeConnection();
PreparedStatement pstmt = conn.prepareStatement("insert into User(id, name, password) values(?, ?, ?)");
pstmt.setString(1, user.getId());
pstmt.setString(2, user.getName());
pstmt.setString(3, user.getPassword());
pstmt.executeUpdate();
pstmt.close();
conn.close();
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.makeConnection();
PreparedStatement pstmt = conn.prepareStatement("select id, name, password from User where id = ?");
pstmt.setString(1, id);
ResultSet rs = pstmt.executeQuery(); // 내가 요청한 결과값이 rs에 담아옴
rs.next();
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
// 안 닫아 주면 계속 연결돼 있음 -> 연결만 늘어나서 서비스 장애가 생길 수 있음
pstmt.close();
conn.close();
rs.close();
return user;
}
public static void main(String[] args) throws SQLException, ClassNotFoundException {
UserDao userDao = new UserDao();
User user = new User();
user.setId("2");
user.setName("huisu");
user.setPassword("2459810");
User selectuser = userDao.get("2");
System.out.println(selectuser.getId());;
System.out.println(selectuser.getName());
System.out.println(selectuser.getPassword());
}
}
생성자 부분에 여전히 this.connectionMaker = new DConnectionMaker();
라는 DConnectionMaker라는 구체적인 클래스에 의존 중이다. 생성자를 호출해서 오브젝트를 생성하는 코드가 여전히 UserDao에 남아 있는 것이다. 따라서 생성자를 통한 DI로 만들어야 한다.
package com.example.tobyspring3.dao;
import com.example.tobyspring3.domain.User;
import java.sql.*;
import static java.lang.System.getenv;
public class UserDao {
ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.makeConnection();
PreparedStatement pstmt = conn.prepareStatement("insert into User(id, name, password) values(?, ?, ?)");
pstmt.setString(1, user.getId());
pstmt.setString(2, user.getName());
pstmt.setString(3, user.getPassword());
pstmt.executeUpdate();
pstmt.close();
conn.close();
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.makeConnection();
PreparedStatement pstmt = conn.prepareStatement("select id, name, password from User where id = ?");
pstmt.setString(1, id);
ResultSet rs = pstmt.executeQuery(); // 내가 요청한 결과값이 rs에 담아옴
rs.next();
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
// 안 닫아 주면 계속 연결돼 있음 -> 연결만 늘어나서 서비스 장애가 생길 수 있음
pstmt.close();
conn.close();
rs.close();
return user;
}
public static void main(String[] args) throws SQLException, ClassNotFoundException {
UserDao userDao = new UserDao(new DConnectionMaker());
User user = new User();
user.setId("2");
user.setName("huisu");
user.setPassword("2459810");
User selectuser = userDao.get("2");
System.out.println(selectuser.getId());;
System.out.println(selectuser.getName());
System.out.println(selectuser.getPassword());
}
}
이후 UserDao class에 있는 main method만 분리시키면 더욱 구조적이게 된다.
package com.example.tobyspring3.dao;
import com.example.tobyspring3.domain.User;
import java.sql.SQLException;
public class UserDaoTest {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao userDao = new UserDao(connectionMaker);
User user = new User();
user.setId("2");
user.setName("huisu");
user.setPassword("2459810");
User selectuser = userDao.get("2");
System.out.println(selectuser.getId());;
System.out.println(selectuser.getName());
System.out.println(selectuser.getPassword());
}
}