스프링 공부 - 1.1, 1.2 정리

haaaalin·2022년 1월 21일
0
post-thumbnail

초난감 DAO

여기서 DAO란?
DAO(Data Access Object)는 DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 Object

먼저 사용자 정보를 저장할 User 클래스를 만들고, User 오브젝트에 담긴 정보가 실제로 보관될 DB의 테이블을 하나 만든다.


✍ 코드 - User 클래스

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


✍ 코드 - DB의 테이블 생성

create table users (
	id varchar(10) primary key,	
	name varchar(20) not null,
	password varchar(10) not null
)

UserDao


사용자 정보를 DB에 넣고 관리할 수 있는 DAO 클래스를 만든다.

< DAO 클래스의 기능 >
1. 새로운 사용자 생성
2. 아이디를 이용해 사용자 정보 조회


✍ 코드 - UserDao 클래스

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?characterEncoding=UTF-8", "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?characterEncoding=UTF-8", "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;
	}

}


UserDao 클래스는 JDBC API 를 이용해 작성한 코드이다.

🧐JDBC API 를 사용하는 순서

  1. DB 연결을 위한 Connection을 가져오기
  2. SQL을 담은 Statement를 만들기
  3. Statement 실행
  4. 조회의 경우, SQL 쿼리의 실행 결과를 ResultSet으로 받아서 정보를 저장할 User와 같은 오브젝트로 옮겨주기
  5. 작업 중에 생성된 Statement, Connection, ResultSet 같은 리소스들 작업 마친 후 반드시 삭제
  6. JDBC API 가 만들어내는 예외 처리

main()을 이용한 DAO 테스트 코드

만들어진 코드 기능을 검증하기 위해 main 메소드를 이용하자.


✍ 코드 - main 메소드

	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		UserDao dao = new UserDao();

		User user = new User();
		user.setId("whiteship");
		user.setName("백기선");
		user.setPassword("married");

		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() + " 조회 성공");
	}

책에서는 이 코드를 UserDao 클래스 밑에 작성했지만, 바로 UserDaoTest 클래스를 직접 생성해서 main 메소드를 작성해도 좋다. (어차피 책에서도 나중에 main 메소드를 따로 뺀다.)

⭐ 관심사의 분리

(이번 챕터에서 꽤나 중요하다고 생각하는 내용이다.)

개발자가 객체를 설계할 때 가장 염두에 둬야 할 사항은 바로!
미래의 변화를 어떻게 대비할 것인가 이다.

만약에, 개발자에게 기능 변경을 요청했다고 하자.
그 기능 변경을 하기 위해 무수히 많은 코드를 고쳐야 했다면? 그 고친 코드도 잘 실행되는지 확신할 수 없다면?

모든 변경과 발전은 한 번에 한 가지 관심사항에 집중해서 일어난다.
그렇다면 우리는 그 변경에 대비하기 위해 관심이 같은 것끼리 모으고, 다른 것끼리는 떨어지게 만들어야 한다.

커넥션 만들기의 추출

이제 본격적으로 관심사에 따라 코드를 분리하기 시작한다.

🧐 UserDao 에서 발견되는 3가지 관심사항

  • DB와 연결을 위한 커넥션을 어떻게 가져올까?
  • 사용자 등록/조회를 위해 DB에 보낼 SQL 문장을 담을 Statement 를 만들고 실행
  • 작업이 끝나면 사용한 리소스들을 닫아서 시스템에 돌려주는 것

첫번째로 주목할 관심사는 Connection 오브젝트를 가져오는 부분이다. 이 부분을 분리해야 한다.
왜냐, 일단 다른 관심대상과 얽혀 있고, get()add() 두 메소드에서 중복되어 있어서 나중에 변경이 일어난다면, 지저분하게 꼬여있는 🍝스파게티 코드가 된다.

중복 코드 분리


✍ 코드 - getConnection() 메소드 추출

public class UserDao {
	public void add(User user) throws ClassNotFoundException, SQLException {
		Connection c = getConnection();
		...
	}


	public User get(String id) throws ClassNotFoundException, SQLException {
		Connection c = getConnection();
		...
	}


	private Connection getConnection() throws ClassNotFoundException,
			SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/springbook?characterEncoding=UTF-8", "spring",
				"book");
		return c;
	}


이렇게 함수로 분리해놓으면, DB 종류가 변경되거나, 접속 방법이 바뀌었을 때 getConnection() 의 코드만 수정하면 된다.

✅ 항상 코드를 짤 때에는 극단적 상황을 생각해야 한다. 대표적(?) 극단적 예로는 나중에 수정해야 할 때 같은 코드를 1000000000번 수정해야 하는 경우 ...😅

리팩토링

방금 한 작업은 여러 메소드에 중복돼서 등장하는 특정 관심사항이 담긴 코드를 하나의 메소드로 분리해낸 것이다. 이 작업은 기능에 영향을 주지 않고, 코드의 구조만 변경한다. 이러한 작업을 바로 리팩토링이라고 한다.

그 중 중복된 코드를 이렇게 한 메소드로 추출하는 작업을 메소드 추출 기법이라고 한다.

프로그래머라면 아주아주 중요한 작업!!!!⭐⭐

DB 커넥션 만들기의 독립


이번엔 문제가 발생했다.

문제

  • 이 코드를 사서 이용하겠다는 A사와 B사 등장
  • A사와 B사가 이용하고 있는 DB 종류, DB 커넥션을 가져오는 방법도 각각 다르게 적용하고 싶어한다.
  • 심지어 이후에도 DB 커넥션을 가져오는 방법이 종종 변경될 수 있다.

    + 고객에게는 미리 컴파일된 클래스 바이너리 파일만 제공하고 싶다.(소스코드 제공 X)

상속을 통해 해결해볼까?

우리가 만든 getConnection() 의 구현 내용을 지우고, 이 메소드를 추상 메소드로 만들어 놓는 것이 바로 해결 방법!

A사와 B사는 추상클래스인 UserDao 각각 상속받은 서브클래스를 만들어 getConnection()을 원하는 방향대로 직접 구현하면 된다.

'DB 연결 방법은 어떻게 할 것인가' 라는 관심은 클래스 계층구조를 통해 독립적으로 분리되었다.

이런 것이 바로 템플릿 메소드 패턴
(스프링에서 애용하는 패턴)

템플릿 메소드 패턴이란?

슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 추상 메소드나 오버라이딩이 가능한 Protected 메소드 등으로 제작, 서브 클래스에서 필요에 맞게 구현해서 사용하도록 하는 방법

팩토리 메소드 패턴

UserDao의 서브 클래스의 etConnection() 은 어떤 Connection 클래스의 오브젝트를 어떻게 생성할 지 정하는 방법이다. 이렇게 서브클래스에서 구체적인 오브젝트 생성 방법을 결정하게 하는 것을 팩토리 메소드 패턴이라고 한다.

📁 참고

  • 토비의 스프링 3.1 Vol.1 - 이일민
profile
한 걸음 한 걸음 쌓아가자😎

0개의 댓글