th42500의 TIL

[Spring] UserDao Factory 생성 본문

Backend/Spring & SpringBoot

[Spring] UserDao Factory 생성

th42500 2022. 10. 19. 00:12

오늘은 지금까지 작성했던 UserDao 클래스에 Factory를 적용해봄으로써 역할과 책임에 따른 클래스 분리를 해보고자 한다.

 

❓ Factory란?

✔ 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 것

✔ 오브젝트를 생성하는 쪽과 오브젝트를 사용하는 쪽의 역할과 책임을 깔끔하게 분리하려는 목적으로 사용

해당 Factory는 디자인패턴에서 말하는 특별한 문제를 해결하기 위해 사용되는 추상 팩토리 패턴이나 팩토리 메소드 패턴과는 다르다는 것을 유의하자

 

 

📌 UserDao에 Factory 적용하기

그동안 인터페이스를 생성하여 DB 커넥션을 제공하는 클래스에 대한 구체적인 정보는 모두 제거할 수 있었으나, 아직 UserDao가 인터페이스 뿐만 아니라 구체적인 클래스까지 알고 있다는 문제점이 남아 있었다. 즉, 불필요한 의존관계가 남아있어 이를 해결해야 했다.

지금까지 구현한 구조

 

이번 단계에서는 UserDAO와 ConnectionMaker 구현 클래스의 오브젝트를 만드는 것과 그렇게 만들어진 두 개의 오브젝트가 연결돼서 사용할 수 있도록 관계를 맺어주고자 한다.

즉, UserDao가 어떤 ConnectionMaker 구현 클래스의 오브젝트를 이용할지를 스스로 결정하게 할 수 있도록 만들고자 한다.

 

[UserDao.java]

UserDao에서는 생성자를 통해 Connection을 생성하는 객체를 생성자를 통해 주입받아 사용한다.

package com.sping.dao;

import com.sping.domain.User;

import java.sql.*;
import java.util.List;

public class UserDao {

    private ConnectionMaker connectionMaker;  // ✨

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

    public List<User> selectAll() {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<User> userList = null;

        try {
            conn = connectionMaker.getConnection();
            pstmt = conn.prepareStatement("select * from users");
            rs = pstmt.executeQuery();

            while(rs.next()) {
                User user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password"));
                userList.add(user);
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            ConnectionClose.close(conn, pstmt, rs);
        }

        return userList;
    }

    public User selectById(String sId) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        User user = null;

        try{
            conn = connectionMaker.getConnection();
            pstmt = conn.prepareStatement("select id, name, password from users where id = ?");
            pstmt.setString(1, sId);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                user = new User(rs.getString("id"), rs.getString("name"), rs.getString("password"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            ConnectionClose.close(conn, pstmt, rs);
        }
        return user;
    }

    public void add(User user) {
        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            conn = connectionMaker.getConnection();
            pstmt = conn.prepareStatement("insert into users(id, name, password) values (?, ?, ?)");
            pstmt.setString(1, user.getId());
            pstmt.setString(2, user.getName());
            pstmt.setString(3, user.getPassword());

            pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            ConnectionClose.close(conn, pstmt);
        }
    }

    public void deleteAll() {
        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            conn = connectionMaker.getConnection();
            pstmt = conn.prepareStatement("delete from users");
            pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            ConnectionClose.close(conn, pstmt);
        }
    }

    public int getCount() {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        int cnt = 0;

        try{
            conn = connectionMaker.getConnection();
            pstmt = conn.prepareStatement("select count(*) from users");

            rs = pstmt.executeQuery();

            if (rs.next()) {
                cnt = rs.getInt(1);
            }

        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            ConnectionClose.close(conn, pstmt);
        }

        return cnt;
    }
}

 

[UserDaoFactory.java]

UserDaoFactory 클래스를 통해 Connection을 매개변수로 가지는 UserDao 객체를 return할 수 있도록 한다.

해당 클래스는 UserDao와 ConnectionMaker 구현 클래스의 오브젝트 간 관계를 맺는 책임을 담당하는 코드로, 이전에는 UserDao 생성자에게 해당 책임을 맡게 하였다면 지금은 UserDao의 클라이언트에게 책임을 넘겨주게 되었다.

이런 방법은 객체지향 프로그래밍의 특성 중 하나인 다형성 덕분에 가능하게 된 것이다.

package com.sping.dao;

public class UserDaoFactory {
    public UserDao awsUserDao() {
        ConnectionMaker connectionMaker = new AWSConnectionMaker();
        return new UserDao(connectionMaker);
    }
}

 

[ UserDaoTest.java ]

구현하는 코드가 달라졌으니 UserDaoTest 역시 UserDaoFactory의 awsUserDao()를 통해 UserDao 객체를 생성할 수 있도록 코드를 변경해주자.

package com.sping.dao;

import com.sping.domain.User;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class UserDaoTest {
    UserDao userDao;

    @BeforeEach
    void setUp() {
        userDao = new UserDaoFactory().awsUserDao();
        userDao.deleteAll();
    }

    @Test
    @DisplayName("add기능 & select 테스트")
    void addAndSelectById() {
        User user = new User("0", "Soyeong", "1234");
        userDao.add(user);

        assertEquals("Soyeong", userDao.selectById("0").getName());
    }


    @Test
    @DisplayName("데이터 개수 세기 테스트")
    void getCount() {
        assertEquals(0, userDao.getCount());
        User user = new User("0", "Soyeong", "1234");
        userDao.add(user);

        assertEquals(1, userDao.getCount());
    }

    @Test
    @DisplayName("deletAll 테스트")
    void deleteAll() {
        userDao.deleteAll();
        assertEquals(0, userDao.getCount());
    }

}

 

[ 테스트 결과 ]

테스트 결과 이전과 마찬가지로 잘 실행되는 것을 확인할 수 있다.

Comments