자라선

[Spring] Object DI 본문

Develop/Spring framework

[Spring] Object DI

자라선 2020. 7. 31. 10:34

1. Wrong Code

DAO(Data Access Object)를 사용하여 코드로 알아보자.

하단의 코드로 main, Dao, VO(Value Object)를 생성하였다.


Main Class

package test;

import java.sql.SQLException;
import java.util.Random;

public class main {

	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		UserDao dao = new UserDao();
		
		User user = new User();
		user.setId("" + new Random().nextInt());
		user.setName("test");
		user.setPassword("123");
		
		//dao.add(user);
		System.out.println(dao.get("-594941768"));
		
	} 

}

 

UserDao Class

package test;

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

public class UserDao {

	public void add(User user) throws ClassNotFoundException, SQLException{
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost:13306/test","test","test");
		
		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());
				
		System.out.println("실행 결과 : " + ps.executeUpdate());
		
		ps.close();
		c.close();
	}
	
	public User get(String id) throws ClassNotFoundException, SQLException{
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost:13306/test","test","test");
		
		PreparedStatement ps = c.prepareStatement("select * from USERS where id = ?");
		
		ps.setString(1, id);
		
		ResultSet rs = ps.executeQuery();
		User user = new User();
		user.setId(rs.getString(1));
		user.setName(rs.getString(2));
		user.setPassword(rs.getString(3));
				
		rs.close();
		ps.close();
		c.close();
		
		return user;
	}
}

User VO class

package test;

public class User {

	String id;
	String name;
	String password;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", name=" + name + ", password=" + password + "]";
	}
	
}

실행하면 UserDao의 add, get 메소드 정상 작동 확인된다.

하지만 위의 UserDao는 크나 큰 문제점이 있다.

지금은 비록 2개의 메소드만 가지고있는 작은 코드이지만, 실제 코딩시 100줄~1000줄이 넘어갈수있다.

메소드도 10~30개가 넘어가는 대용량 클래스라면 위의 코드를 활용할수 있겠는가?

만일 어찌저찌하여 DB를 Migration를 하여 DB이관을 해야할때 위의 Connection 객체 수정만 메소드별로 해야한다.

당연히 누가봐도 비정상이며 확장성 및 분리가 안된 냄새나는 코드로 보일것이다.


위의 중복된 코드를 제거하고 확장성 또한 용이하게 개선해보자 한다.

우선 관점별로 분리를 하자. 가장 먼저 DB가 보일것이다.

Connection 객체를 모듈화하여 클래스내에 모든 메소드로 사용되게끔 하자.

 

package test;

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

public class UserDao {

	public void add(User user) throws ClassNotFoundException, SQLException{
		Connection c = getConnection();
		
		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());
				
		System.out.println("실행 결과 : " + ps.executeUpdate());
		
		ps.close();
		c.close();
	}
	
	public User 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();
		User user = new User();
		user.setId(rs.getString(1));
		user.setName(rs.getString(2));
		user.setPassword(rs.getString(3));
				
		rs.close();
		ps.close();
		c.close();
		
		return user;
	}
	
	public Connection getConnection() throws SQLException, ClassNotFoundException{
		Class.forName("com.mysql.jdbc.Driver");
		return DriverManager.getConnection("jdbc:mysql://localhost:13306/test","test","test");
	}
}

getConnection() 메소드로 모듈화 하여 비교적 정돈된 코드와 DB변동시 getConnection() 메소드만 수정하여 모든 객체에 전달할수 있다.


2. Abstract Extention Module

이 UserDao 객체를 어떤 DB던 사용자에 따라 커스텀을 하고싶을때 어떻게 해야할까?

A사 와 B사가 둘다 사용하자고 할때 A는 Oracle, B는 Mysql를 쓰고싶어한다.

그럼 우린 getConnection() 메소드를 확장성 가능토록 수정해주어야한다.

public abstract class UserDao {
...
public abstract Connection getConnection() throws SQLException, ClassNotFoundException;
}
package test;

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

public class Bcompany extends UserDao {
	@Override
	public Connection getConnection() throws SQLException, ClassNotFoundException {
		Class.forName("com.mysql.jdbc.Driver");
		return DriverManager.getConnection("jdbc:mysql://localhost:13306/test","test","test");
	}
}

Bcompany 클래스와 Acompany 클래스(코드는 어차피 같아서 안썻다.)는 UserDao를 상속하여

getConnection()을 Override하여 각자만의 DB에 Connection객체를 만든다.

그러면 A나 B가 아닌 C,D,E 등등.. getConnection()만 Override 한다면 언제든 원하는 DB로 접속이 가능하다.

이러한 패턴을 보고 템플릿 메소드 패턴(Template Methed Parttern) 이라고 한다.

확장성이 뛰어나며, UserDao는 Connection만 넘겨주면 되기에 신경쓸 필요가 없다.

Acompany와 Bcompany 둘다 상속하여 정의한다.

하지만. JAVA 특성상 다중상속은 허용되지 않는다.

즉. 위의 코드로 인해 상속을 강제적으로 해야한다는 점에서 실용적이지 못하다.


3. getConnection for another class

그러면 상속이 아닌 UserDao가 다른 클래스의 getConnection를 가져오면 된다.

	ConnectionMaker conn = new ConnectionMaker(); 
	
	public void add(User user) throws ClassNotFoundException, SQLException{
		Connection c = conn.getConnection();
public class ConnectionMaker {
	public Connection getConnection() throws SQLException, ClassNotFoundException {
		Class.forName("com.mysql.jdbc.Driver");
		return DriverManager.getConnection("jdbc:mysql://localhost:13306/test","test","test");
	}
}

그러면 A사와 B사는 ConnectionMaker 클래스를 만들어 생성하여 getConnection()메소드를 정의해주면된다.

하지만 뭔가 이상하다;

하지만 이렇게 되면 A, B사 모두 강제적으로 ConnectionMaker 클래스를 만들어야 하며 getConnection()메소드를 만들어줘야한다?

ConnectionMaker 라는 네이밍룰도 지켜줘야 하는것만 봐도 위의 2번 보다 더 강제적이며 효율적이지 못하다.


4. Interface 사용

Interface를 선언후 getConnection을 하면 해당 인터페이스를 implements한 객체는 필수적으로 생성해야한다.

그 후 인터페이스를 상속받은 클래스에 해당 DBconnection를 만들어주면 된다.

package test;

import java.sql.Connection;
import java.sql.SQLException;
public interface ConnectionMaker {
	public Connection getConnection() throws SQLException, ClassNotFoundException;
}
package test;

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

public class ConnectionMakerImpl implements ConnectionMaker{
	@Override
	public Connection getConnection() throws SQLException, ClassNotFoundException {
		Class.forName("com.mysql.jdbc.Driver");
		return DriverManager.getConnection("jdbc:mysql://localhost:13306/test","test","test");
	}
}
public class UserDao {
	ConnectionMaker connectionMaker;
	public UserDao() {
		connectionMaker = new ConnectionMakerImpl();
	}

	public void add(User user) throws ClassNotFoundException, SQLException{
		Connection c = connectionMaker.getConnection();
		
		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());
				
		System.out.println("실행 결과 : " + ps.executeUpdate());
		
		ps.close();
		c.close();
	}
..
}

이렇게 되면 우린 Interface만 A사나 B사에게 던지면 해당 메소드에 맞춰 개발만 하면된다.

하지만 생성자의 new로 인해 Impl의 종속성은 없어지지 않았다....


5. JAVA polymorphism (다양성)

4번은 UserDao에 getConection를 받기 위한 new 생성자를 사용하여 가져오도록 되어있다.

하지만 ConnectionMakerImpl 객체를 A사나 B사도 개발을 해야하며, UserDao의 내부 소스를 알아야하기 때문에 유효한 코드는 아니다.

이러한 종속성을 제거하기 위하여 JAVA의 다양성을 사용하여 객체를 받아올수 있다.

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

	public void add(User user) throws ClassNotFoundException, SQLException{
		Connection c = connectionMaker.getConnection();
		
		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());
				
		System.out.println("실행 결과 : " + ps.executeUpdate());
		
		ps.close();
		c.close();
	}
..
}

UserDao는 생성자를 통해 ConnectionMaker 객체만 가져오면 문제가 되었던 종속성이 해결이 된다.

UserDao는 다른 객체와 상관없이 ConnectionMaker객체만 가져와 getConnection()를 실행할수 있다.

그러면 Interface와 종속성 모두 해결될수 있고 확장성 또한 용이해진다.

 

이렇게 Injection받아서 사용하는 방법이 스프링에서의 DI 개념이다

'Develop > Spring framework' 카테고리의 다른 글

[Spring] IoC Container  (0) 2020.07.31
[Spring] CGLIB is required to process  (0) 2020.07.31
[Spring] IoC 제어역전  (0) 2020.07.31
[Spring] Factory  (0) 2020.07.31
[Spring] 개방 폐쇄 원칙  (0) 2020.07.31
Comments