Spring에 들어가기 앞서 이 초난감 DAO 라는 파트로 시작한다
우선 초 난감 DAO 코드를 보면 단순 JAVA로 DB를 구헝하는 코드이다.
user1 entity
public class User1 {
String id;
String name;
String passWord;
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getPassWord() {
return passWord;
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}
user1 dao
DB mysql 과 연결하며 user1을 생성(저장) 및 조회한다
public class User1Dao {
// create user
public void add(User1 user1) throws ClassNotFoundException , SQLException{
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook","root","root");
PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values(?,?,?)");
ps.setString(1,user1.getId());
ps.setString(2,user1.getName());
ps.setString(1,user1.getPassWord());
ps.executeUpdate();
ps.close();
c.close();
}
// read user
public User1 get(String id) throws ClassNotFoundException, SQLException{
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook","root","root");
PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
ps.setString(1,id);
ResultSet rs = ps.executeQuery();
rs.next();
User1 user = new User1();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassWord(rs.getString("password"));
rs.close();
ps.close();
c.close();
return user;
}
}
user1 생성 조회 테스트
public class User1Test {
@Test
public void userCreateTest() throws SQLException, ClassNotFoundException {
User1Dao user1Dao = new User1Dao();
User1 user1 = new User1();
user1.setId("witeship");
user1.setName("백기선");
user1.setPassWord("married");
user1Dao.add(user1);
User1 findUser = user1Dao.get(user1.getId());
Assert.assertEquals(findUser,user1.getId());
}
}


위와 같이 db에 잘 담기고 test도 정상적으로 수행되는것을 확인할수 있다. 그럼 위 코드는 무엇이 난감한 코드라는 것일까?
사용자의 비즈니스 프로세스와 그에따른 요구사항은 끈임없이 바뀌고 발전한다 애플리케이션이 기반을 두고 있는 기술도 시간이 지남에 따라 바뀌고 운영되는 환경도 변화한다.
개발자가 객체를 설계 할때 가장 염두에 둬야할 사항은 미래 변화를 어떻게 대비할 것인가이다.
변경에 대한 요청은 대체로 집중이 한가지 관심에 대해 일어나지만 그에 따른 작업은 한곳에 집중되지 않는 경우가 많다
위코드에서 DB 접속 암호를 변경하려고 DAO 클래스 수백개를 모두 수정해야한다면
ex)..
// user1DAO
public class User1Dao {
// create user
public void add(User1 user1) throws ClassNotFoundException , SQLException{
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook","root","root");
public class Product {
// create product
public void add(Product product) throws ClassNotFoundException , SQLException{
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook","root","root");
다음처럼 여러 entity가 존재할시 각 entity 마다 DB를 연결해줘야함
여기서 관심사 분리라는 개념이 나온다
관심사 분리란: 관심이 같은 것 끼리는 하나의 객체 안으로 또는 친한 객체로 모이게 하고 , 관신이 다른 것은 서로 영향을 주지 않도록 분리
위 UserDoa 의 구현된 메소드를 다시 살펴보면 add() 메소드 하나에서 적어도 세가지 관심사를 발견할 수 있다.
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook","root","root");
PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values(?,?,?)");
ps.setString(1,user1.getId());
ps.setString(2,user1.getName());
ps.setString(1,user1.getPassWord());
ps.executeUpdate();
ps.close();
c.close();
리소스 돌려주는 것은 https://velog.io/@limjongheok/finalizer-%EC%99%80-cleaner-%EC%82%AC%EC%9A%A9%EC%9D%84-%ED%94%BC%ED%95%98%EB%9D%BC 보기
우선 첫번째 관심사를 보자
DB 연결을 위해 Connection 오브젝트를 가져온다 현재 DB 커넥션을 가져오는 코드는 다른 관심사와 섞여 같은 add() 메서드에 담겨 있며,밑에 get 메소드에도 중복되어 있다. 현재에은 add() get() 만 사용하였지만 앞으로 수백개 수천개의 메소드들이 만들때마다 Connection 생성은 중복될것이다.
그리고 만약 Connection이 변한다고 생각해 보자 MySQL -> Oracle 그럼 또 해당 메서드드 들을 일일이 수정해야 할 것이다.
우선 위에 말한 connection 중복코드를 분리해보자
public class User2Dao { public void add(User1 user1) throws ClassNotFoundException , SQLException {
Connection c = getConnection();
PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values(?,?,?)");
ps.setString(1,user1.getId());
ps.setString(2,user1.getName());
ps.setString(1,user1.getPassWord());
ps.executeUpdate();
ps.close();
c.close();
}
// read user
public User1 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();
User1 user = new User1();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassWord(rs.getString("password"));
rs.close();
ps.close();
c.close();
return user;
}
// 추출
public Connection getConnection() throws SQLException {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook","root","root");
return c;
}
}
다음과 같이 getConnection() 메서드를 만들어 추출해 보았다.
이제 만약 DB 가 변경되었거나 DB 정보가(비밀번호 등) 이 변화 하였으면 getConnection() 안에서만 수정면 된다.
만약 UserDao 소스코드를 고객 스스로 원하는 DB 커넥션 방식을 적용해 가면서 UserDao 방식을 사용하게 할 수 있을까?
우선 할수 있는 생각으로는 UserDao 코드를 한단계 더 분리하는 것이다

이런식으로 하면 고객이 원하는 DB 커넥션을 스스로 적용해 갈수 있을 것이다.
UserDao 추상화
public abstract class UserDao3 {
public void add(User1 user1) throws ClassNotFoundException, SQLException{
Connection c = getConnection();
PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values(?,?,?)");
ps.setString(1,user1.getId());
ps.setString(2,user1.getName());
ps.setString(1,user1.getPassWord());
ps.executeUpdate();
ps.close();
c.close();
}
public User1 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();
User1 user = new User1();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassWord(rs.getString("password"));
rs.close();
ps.close();
c.close();
return user;
}
public abstract Connection getConnection() throws SQLException;
}
n사 dao
public class NUserDao extends UserDao3{
@Override
public Connection getConnection() throws SQLException {
// n사 mysql
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook","root","root");
return c;
}
}
d사 dao
public class DUserDao extends UserDao3{
@Override
public Connection getConnection() throws SQLException {
// d사 oracle
Connection c = DriverManager.getConnection("jdbc:oracle://localhost:3306/springbook","root","root");
return c;
}
}
이런식으로 클래스 계층구조로 분리되며 UserDao 코드를 변경없이 새로운 클래스 (n사 , d사) 와 같이 추가하여 각자의 db 설정 정보만 넘기면 된다
위와 같은 방식을 템플릿 메소드 패턴이라 한다
또한 서브 클래스에서 구체적인 오브젝트를 생성 방법을 결정하는 것을 팩토리 메소드 패턴이라 한다
위처럼 User Dao는 어떤 기능을 사용하는 데만 관심이 있고 NUserDao 나 DUserDao에서 어떤식으로 Connection 기능을 제공하는지 관심을 두고 있다

이 방식은 상속을 사용했다는 단점이 있다.
만약
지금까지는 메소드, 계층 구조로 분리를 했다면 이번엔 완전 독립적인 클래스로 분리하는 것이다.
클래스 분리
public class SimpleConnectionMaker {
public Connection getConnection() throws ClassNotFoundException, SQLException{
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook","root","root");
return c;
}
}
public class UserDao4 {
private SimpleConnectionMaker simpleConnectionMaker;
public UserDao4(){
simpleConnectionMaker = new SimpleConnectionMaker();
}
public void add(User1 user1) throws ClassNotFoundException, SQLException {
Connection c = simpleConnectionMaker.getConnection();
PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values(?,?,?)");
ps.setString(1,user1.getId());
ps.setString(2,user1.getName());
ps.setString(1,user1.getPassWord());
ps.executeUpdate();
ps.close();
c.close();
}
public User1 get(String id) throws ClassNotFoundException, SQLException{
Connection c = simpleConnectionMaker.getConnection();
PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
ps.setString(1,id);
ResultSet rs = ps.executeQuery();
rs.next();
User1 user = new User1();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
user.setPassWord(rs.getString("password"));
rs.close();
ps.close();
c.close();
return user;
}
}
문제
추상화
두 클래스가 서로 긴밀하게 연결되어 있지 않도록 중간에 추상적인 누슨한 연결고리 만들기
어떤 공통적인 성격을 뽑아 내어 따로 분리

public interface ConnectionMaker {
Connection makeConnection() throws ClassNotFoundException, SQLException;
}
public class DConnectionMaker implements ConnectionMaker{
@Override
public Connection makeConnection() throws ClassNotFoundException, SQLException {
// 독자적 방법으로 Connection 생성
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook","root","pdw06135@");
return c;
}
}
public class UserDao5 {
private ConnectionMaker connectionMaker;
public UserDao5(){
connectionMaker = new DConnectionMaker();
}
public void add(User1 user1) throws ClassNotFoundException, SQLException{
Connection c = connectionMaker.makeConnection();
// 기존 코드와 동일
}
public User1 get(User1 user1) throws ClassNotFoundException, SQLException{
Connection c = connectionMaker.makeConnection();
// 기존 코드와 동일
}
}
다음은 특정 인터페이스를 사용해 db 커넥션을 가져와 사용하도록 수정한 userDAO이다
connectionMaker = new DConnectionMaker();
여전히
다음과 같이 DConnection를 생성자를 호출해 생성하는 코드가 여전히 남아있다.
초기에 한번 어떤 클래스의 오브젝트를 사용할 지 결정하느느 생성자의 코드는 제거되지 않고 남아 있다.
앞서 여전히 UserDao 에는 어떤 ConnectionMaker 구현 클래스를 사용할지 결정 코드가 남아있다.
이때문에 인터페이스 이용한 분리에도 불구하고 여전히 UserDao 변경없이 DB 커넥션 기능의 확장이 자유롭지 못한데
그 이유는 UserDao 안에 분리되지 않은 또 다른 관심 사항이 존재하기 때문이다
UserDao 에는 어떤 ConnectionMaker 구현 클래스를 사용할지 결정하는 new DConnectionMaker() 라는 코드가 있는데 (UserDao 가 어떤 ConnectionMaker 사이 관계 설정해주는 것) 이 관심이다 이 관심사를 분리하지 않으면 UserDao는 독립적인 확장 클래스가 될 수없다
오브젝트 사이 관계가 만들어질시 일단 만들어진 오브젝트가 존재해야한다
connectionMaker = new DConnectionMaker();
허나 외부에서 만들어준 것을 가져오는 방식도 있다.

다음과 같이 외부에서 만든 오브젝트를 전달받으려면 메소드 파라미터나 생성자 파라미터를 이용하면 된다
클라이언트 (UserDao 사용자) 가

다음과 같이 관계를 만듦
public UserDao6(ConnectionMaker connectionMaker){
connectionMaker = connectionMaker;
}
여기서 앞선 DConnectionMaker 가 사라진 것을 볼수 있다. DConnectionMaker 를 생성하는 코드는 UserDao 와 특정 ConnctionMaker 구현 클래스의 오브젝트 간 관계를 맺는 책임을 담당하는 코드였는데 UserDao 클라이언트(사용자) 에게 넘겨 버렸기때문
public class UserDao6Test {
@Test
public void UserDao6Test(){
ConnectionMaker2 connectionMaker2 = new DConnectionMaker2();
UserDao6 userDao6 = new UserDao6(connectionMaker2);
}
}
다음과 같이 클라이언트가 DConnectionMaker 를 정해줌
UserDao6Test 클래스는 UserDao 와 ConnectionMaker 수현 클래스와의 런타임 오브젝트 의존관계를 설정하는 책임을 담당한다.
@Test
public void UserDao6DTest(){
ConnectionMaker2 connectionMaker2 = new DConnectionMaker2();
UserDao6 userDao6 = new UserDao6(connectionMaker2);
}
@Test
public void UserDao6NTest(){
ConnectionMaker2 connectionMaker2 = new NConnectionMaker2();
UserDao6 userDao6 = new UserDao6(connectionMaker2);
}
다음과 같이 모든 고객이 만족스럽게 db 연결 기능을 확장해 사용할 수 있다.
이렇게 인터페이스를 도입하고 클라이언트의 도움을 얻는 방법은 상속을 사용해 비슷한 시도를 했을 경우 비해 훨씬 유연하다
어떤 한 관심사를 한 클래스에 모아 놓는것
느슨하게 연결된 형태가 설계상 더 선호된다
ConnectionMaker 인터페이스 도입으로 DB 연결 기능을 구현한 클래스가 바뀌더라도 dao 코드는 변경될 필요가 없다
더 나아가 ConnectionMaker 클래스 결정책임을 DAO 클라이엍느로 분리한 덕분에 ConnectionMaker 구현 클래스 바껴도 DAO 클래스 코드 수정할 필요 없다
응집도가 높으면 사용자의 데이터 처리 긴으이 흩어지지 않고 DAO 안에 이해하기 쉽고 깔끔하게 모여 자신을 책임 담당하는데만 충실
관계(결합) 을 느슨하게 연결데 UserDao 는 구체적인 ConnectionMaker 구현 클래스 알 필요 없고 신경 쓰지 낳아도됨
개선한 구조를 디자인 패턴에 보면 전략 패턴이다
전략 패턴은 자신 기능 맥락에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴 이다
1.1~1.3 까지 점점 순차적으로 리팩토링을 통해 코드를 개선하는 방식으로 설명을 해주는 것이 좋았다. 왜 이렇게 변해야하고 이렇게 변했을때는 무엇인 문제인지를 알 수 있었고 그동안 생각없이 의존성 주입을 사용해 왔었는데 무슨 이유 때문에 의존성 주입이 나왔는지 알게 될 수 있었어 좋았던 책이 였다.