토비의 스프링3 - 1부 이해 1.1~1.3

2020. 5. 25. 13:48자바 Java/스프링(Spring) 프레임워크

반응형

 

1.1 초난감 DAO , 1.2 DAO의 분리, 1.3 DAO의 확장 

데이터를 DB에 연동해서 저장하려고 할때 코드는 다음과 같다. 

package springbook.user.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import springbook.user.domain.User;

public class UserDao {
	public void add(User user) throws ClassNotFoundException,SQLException{
		Class.forName("com.mysql.jdbc.Driver");
		Connection c =DriverManager.getConnection(
				"jdbc:mysql://localhost/springbook","spring","book");
		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());
		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/springbook","spring","book");
		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("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("iluvdori");
		user.setName("토끼와거북");
		user.setPassword("queen");
		dao.add(user);		
		System.out.println(user.getId()+" 등록 성공");		
		User user2=dao.get(user.getId());
		System.out.println(user2.getName());		
		System.out.println(user2.getPassword());
		System.out.println(user2.getId()+" 조회 성공");		
	}
}

 

package springbook.user.domain;

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;}
}

 

위 코드의 문제 

중복되는 것이 너무 많다 -> 중복 코드의 메소드를 추출해서 정리하자 

 

 

 

메소드 추출(extract method) 기법을 이용하여 중복된 코드를 뽑아내어 작성한 코드는 좀더 간결해 진다.

	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());
		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("id"));
		user.setName(rs.getString("name"));
		user.setPassword(rs.getString("password"));
		rs.close();
		ps.close();
		c.close();
		return user;
	}

	private Connection getConnection() throws ClassNotFoundException, SQLException {
		Class.forName("oracle.jdbc.driver.OracleDriver");
		Connection c = DriverManager.getConnection("jdbc:oracle:thin:@localhost:11521:xe", "java", "pwd");
		return c;
	}

 

하지만 여기에도 문제가 있다. 

다음 코드를 실행시켜 보면 처음에는 데이터가 잘 등록 되지만 

두번째 실행 시켰을 때는 에러가 발생한다. 

 

 

처음 실행 시켰을 때 결과

 

 

 

두번째 실행 시켰을 때 에러 발생 

 

 

따라서 main() 메소드 테스트를 다시 실행하기 전에 User 테이블의 사용자 정보를 모두 삭제해야 된다. 

그러면 다시 제대로 작동 할 것이다. 

 

 

 

 

추상화란 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업이다. 

자바는 추상화를 위해 제공하는 가장 유용한 도구는 바로 인터페이스 이다. 

[인터페이스에 대해서 다시 공부하고 싶다면  참고하자 : https://joy-baek.tistory.com/15 ]

 

인터페이스는 어떤 일을 하겠다는 기능만 정의해 놓은 것이다. 

그 기능을 어떻게 구현할 것인지는 사용할 클래스들이 알아서 결정할 것이다. 

 

 

일단 리스트는 위와 같다. 인터페이스를 이용해서 비어있는 메소드를 만들고 D사 에서 자신들의 DB에 맞는 연결방법을 DConnectionMaker 클래스에서 작성할수 있도록 만드는 것이다. 

그렇게 하면 효율적인 틀을 만들면서 사용자가 자신의 규격에 맞게 사용 할 수 있을 것이다. 

또한 UserDaoTest 클래스를 사용 하여  UserDao와 ConnectionMaker  구현 클래스와의 런타임 오브젝트 의존관계를 설정하는 책임을 담당하게 했다. 이렇게 함으로써 UserDao의 변경 없이 사용자가 DB접속 클래스를 만들어 UserDao를 사용할 수 있게 되었다. 

package springbook.user.dao;
import java.sql.Connection;
import java.sql.SQLException;

public interface ConnectionMaker {
	public Connection makeConnection() throws ClassNotFoundException, SQLException;
}

 

 

package springbook.user.dao;

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

public class DConnectionMaker implements ConnectionMaker {
	public Connection makeConnection() throws ClassNotFoundException, SQLException {
		Class.forName("oracle.jdbc.driver.OracleDriver");
		Connection c = DriverManager.getConnection("jdbc:oracle:thin:@localhost:11521:xe", "java", "pwd");
		return c;
	}
}

 

 

package springbook.user.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import springbook.user.domain.User;

public class UserDao {

	private ConnectionMaker connectionMaker;

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

	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = connectionMaker.makeConnection();
		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());
		ps.executeUpdate();
		ps.close();
		c.close();
	}

	public User get(String id) throws ClassNotFoundException, SQLException {
		Connection c = connectionMaker.makeConnection();
		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("id"));
		user.setName(rs.getString("name"));
		user.setPassword(rs.getString("password"));
		rs.close();
		ps.close();
		c.close();
		return user;
	}

}

 

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 {
		ConnectionMaker connectionMaker=new DConnectionMaker(); //UserDao가 사용할 ConnectionMaker 구현 클래스를 결정하고 오브젝트 만듬.
		
		UserDao dao = new UserDao(connectionMaker); // 1. UserDao생성  2. 사용할 ConnectionMaker 타입의 오브젝트 제공. 결국 두 오브젝트 사이의 의존관계 설정 효과 
		User user = new User();
		user.setId("iluvdori");
		user.setName("토끼와거북");
		user.setPassword("queen");
		dao.add(user);
		System.out.println(user.getId() + " 등록 성공");
		User user2 = dao.get(user.getId());
		System.out.println(user2.getName());
		System.out.println(user2.getPassword());
		System.out.println(user2.getId() + " 조회 성공");
	}
}

 

 

 

 

 

 

 

<용어 정리>

DAO(Data Access Object)는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트를 말한다. 

자바빈(JavaBean)은 원래 비주얼 툴에서 조작 가능한 컴포넌트를 말한다. 자바의 주력 개발 플랫폼이 웹 기반의 엔터프라이즈 방식으로 ㅂ뀌면서 비주얼 컴포넌트로서 자바빈은 인기를 잃어갔지만 자바빈의 몇가지 코딩 관례는 JSP빈, EJB와 같은 표준 기술과 자바빈 스타일의 오브젝트를 사용하는 오픈 소스 기술을 통해 이어져왔다. 이제는 자바빈은  다음의 두가지 관례를 따라 만들어진 오브젝트를 가르킨다. 간단히 빈이라고도 부른다. 

         디폴트 생성자 : 자바빈은 파라미터가 없는 디폴트 생성자를 갖고 있어야 한다. 툴이나 프레임 워크에서 리플렉션을 이용해 오브젝트를 생성하기 때문에 필요하다 

         프로퍼티 : 프로퍼티는 set으로 시작하는 수정자 메소드(setter)와 get으로 시작하는 접근자 메소드(getter)를 이용해 수정 또는 조회 할 수 있다. 

리팩토링은 기존의 코드를 외부의 동작방식에는 변화없이 내부구조를 변경해서 재구성하는 작업 또는 기술을 말한다. 리팩토링을 하면 생산성은 올라가고 코드 품질이 높아지고 유지보수 용이하게 된다. 

 

디자인패턴 : 소프트웨어 설계시 특정 상황에서 자주 만나는 문제를 해결하기 위해 사용할 수 있는 재사용 가능한 솔류션을 말한다. 모든 패턴에는 간결한 이름이 있어서 잘 알려진 패턴을 적용하고자 할 때, 간단히 패턴 이름을 언급 하는 것만으로도 설계의 의도와 해결책을 함께 설명 할 수 있다는 장점이 있다. 객체 지향적인 설계로 부터 문제를 해결하기 위해 적용하는 확장성 추구방법은 클래스상속과 오브젝트 합성으로 대부분 정리된다.  **패턴의 핵심이 담긴 목적이나 의도가 중요하다 

 

템플릿 메소드 패턴 : [서브클래스에서 구체적인 오브젝트 생성방법을 결정하게 하는 것] 상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법. 변하지 않는 기능은 슈퍼클래스에 만들어두고 자주 변경되며 확장할 기능은 서브클래스 에서 만든다. 슈퍼 클래스에서는 미리 추상메소드 또는 오버라이드 가능한 메소드를 정의해두고 이를 활용해 코드의 기본 알고리즘을 담고 있는 템플릿 메소드를 만든다. 슈퍼클래스에서 디폴트 기능을 정의해두거나 비워뒀다가 서브클래스에서 선택적으로 오버라이드 할 수 있도록 만들어둔 메소드를 훅(hook) 메소드 라고 한다. 서브클래스에서는 추상메소드를 구현하거나, 훅 메소드를 오버라이드 하는 방법을 이용해 기능의 일부를 확장한다. 

 

팩토리 메소드 패턴 : 템플릿 메소드 패턴과 마찬가지로 상속을 통해 기능을 확장하게 하는 패턴이다. 구조도 비슷. 슈퍼클래스 코드에서는 서브클래스에서 구현할 메소드를 호출해서 필요한 타입의 오브젝트를 가져와 사용한다. 이 메소드는 주로 인터페이스 타입으로 오브젝트를 리턴하므로 서브클래스에서 정확히 어떤 클래스의 오브젝트를 만들어 리턴할지는 슈퍼클래스에서는 알지못한다. 서브클래스는 다양한 방법으로 오브젝트를 생성하는 메소드를 재정의 할 수 있다. 이렇게 서브클래스에서 오브젝트 생성 방법과 클래스를 결정할 수 있도록 미리 정의해둔 메소드를 팩토리 메소드라고 하고, 이 방식을 통해 오브젝트 생성방법을 나머지 로직, 즉 슈퍼클래스의 기본 코드에서 독립시키는 방법을 팩토리 메소드 패턴이라고 한다. 

자바에서는 종종 오브젝트를 생성하는 기능을 가진 메소드를 일반적으로 팩토리 메소드라고 부르기도 한다. 이때 말하는 팩토리 메소드와 팩토리 메소드 패턴의 팩토리 메소드는 의미가 다르므로 혼동하지 않도록 주의한다. 

 

객체지향 설계 원칙  (SOLID) 5가지 

객체지향 프로그래밍 언어는 종류도 다양하고 객체지향 기술을 받아들이고 적용하는 관점과 기법도 차이가 있다 하지만 객체지향으로 묶을 수 있는 분명한 특징이 있다. (디자인 패턴이 특정한 상황에 발생하는 문제에 대한 구체적 솔루션이라면 객체지향 설계 원칙은 일반적인 상황에서 적용가능한 기준이라고 볼수 있다. 객체지향 디자인 패턴은 대부분 객체지향 설계 원칙을 잘 지켜서 만들어져있다. ) 

 

SRP(The Single Responsibility Principle): 단일 책임 원칙 

OCP(The Open Closed Principle): 개방 폐쇄 원칙  - 클래스나 모듈은 확장에는 열려있어야 하고 변경에는 닫혀있어야 한다 .

LSP(The Liskov Substitution Principle): 리스코프 치환 원칙 

ISP(The Interface Segregation Principle): 인터페이스 분리 원칙 

DIP(The Dependency Inversion Principle) : 의존 관계 역전 원칙 

 

개방 폐쇄 원칙은 높은 응집도와 낮은 결합도(high coherence and low coupling) 라는 소프트웨어 개발의 고전적인 원리로도 설명 가능 하다.

 

응집도가 높다는 것은 하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되어 있다는 뜻이다. 높은 응집도는 클래스 레벨 뿐 아니라 패키지, 컴포넌트 모듈에 이르기 까지 그 대상의 크기가 달라도 동일한 원리로 적용 될 수 있다. 

(즉 변경이 일어날때 모듈의 많은 부분이 함께 바뀌는 것이 응집도 높은것, 만약 모듈의 일부분에만 변경이 일어나도 된다면 모듈 전체에서 어떤 부분이 바뀌어야 하는지 파악 해야 하고, 또 그 변경으로 인해 바뀌지 않는 부분에는 다른 영향을 미치지는 않는지 확인해야 하는 이중의 부담이 생길 수 있다. ) 

 

 

결합도가 낮다는 것은 책임과 관심사가 다른 오브젝트 또는 모듈과 느슨하게 연결된 형태를 유지하는 것이다. 변화에 대응하는 속도가 높아지고 구성이 깔끔해진다. 여기서 결합도란 '하나의 오브젝트가 변경이 일어날 때에 관계를 맺고 있는 다른 오브젝트에게 변화를 요구하는 정도' 라고 설명 할 수 있다. 낮은 결합도랑 결국 하나의 변경이 발생 할때 여타 모듈과 객체로 변경에 대한 요구가 전파되지 않는 상태를 말한다. 

 

마지막으로 개선한 UserDaoTest-UserDao-ConnectionMaker 구조를 디자인 패턴의 시각으로 보면 전략 패턴(자주사용되는 패턴)에 해당한다. 전략 패턴은 자신의 기능 맥락(context)에서, 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리 시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴이다. 

 

 전략 패턴의 기준에서 위의 코드를 이야기 해보자면 

컨텍스트(UserDao)를 사용하는 클라이언트(UserDaoTest)는 컨텍스트가 사용할 전략(Connectionmker를 구현한 클래스 예를 들면 DConnectionMaker)을 컨텍스트의 생성자 등을 통해 제공한다. 

 

 

 

 

 

 

<JDBC를 이용하는 작업의 일반적인 순서>

DB연결을 위한 Connection을 가져온다. 

SQL을 담은 Statement (또는 PreparedStatement)를 만든다. 

만들어진 Statement 를 실행한다. 

조회의 경우 SQL쿼리의 실행결과를 ResultSet으로 받아서 정보를 저장할때 오브젝트에 옮겨준다. 

작업 중에 생성된 Connectio, Statement, ResultSet 같은 리소스는 작업을 마친 후 반드시 닫아준다. 

JDBC API가 만들어내는 예외를 잡아서 직접 처리하거나, 메소드에 throws 를 선언해서 예외가 발생하면 메소드 밖으로 던지게 한다. 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형