ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 1.2.3 DB 커넥션 만들기의 독립
    책/토비의 스프링 2024. 7. 7. 14:42

    책을 읽고 흘려버리기 보단, 꼭꼭 씹어먹기 위해 정리해봅니다.

    이 포스팅은 이일민님의 <토비의 스프링3>에 기반하고 있습니다.


     

    1. 문제의 상황

    1) UserDao가 인기를 얻어 여러 회사에서 사용하고 싶어하는 상황

    2) 고객사별로 데이터베이스 연결에 대한 다양한 요청사항이 있음 

    • MySQL, PostgreSQL 등 고객사별로 다양한 데이터베이스 사용 니즈
    • 혹은 동일한 데이터베이스를 사용해도 커넥션을 생성하는 로직이 상이함

    3) UserDao의 소스코드는 기밀이므로 고객사에 노출하지 않아야함

     

     

    2. 해결방법

    고객사가 각자 회사의 입맛에 맞는 커넥션 로직을 구현할 수 있도록 방법을 제공하고, 구현된 로직을 UserDao에서 사용할 수 있도록 변경이 필요

     

     

    3. 상속을 통한 문제해결

    2번의 해결방법을 추상메소드를 상속하는 방법으로 해결한다.

    더보기
    public abstract class UserDao {
      public void add(User user) throws SQLException, ClassNotFoundException {
        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 userId) throws SQLException, ClassNotFoundException {
        Connection c = getConnection();
    
        PreparedStatement ps = c.prepareStatement("select * from users where id = ?");
        ps.setString(1, userId);
        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 void deleteAll() throws ClassNotFoundException, SQLException {
        Connection c = getConnection();
    
        PreparedStatement ps = c.prepareStatement("delete from users");
        ps.executeUpdate();
    
        ps.close();
        c.close();
      }
    
      public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
    
    }
    

     

    UserDao를 추상 클래스로 변경한다.

    // 변경 전
    public class UserDao {
    	...
    }
    
    
    // 변경 후
    public abstract class UserDao {
    	...
    }

     

    그리고 기존의 getConnection 메소드를 추상메소드로 변경한다

      // 변경 전
      public Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook", "root", "12345678");
    
        return c;
      }
      
      
      // 변경 후
      public abstract Connection getConnection() throws ClassNotFoundException, SQLException;

     

    이제 UserDao 를 구매한 회사는 각자 회사의 커넥션 로직을 담은 getConnection 함수를 구현한다.

    // N사
    public class NUserDao extends UserDao {
    
      @Override
      public Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook", "root", "12345678");
    
        System.out.println("Do sth special for Company N");
    
        return c;
      }
    }
    
    // D사
    public class DUserDao extends UserDao {
    
      @Override
      public Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook", "root", "12345678");
    
        System.out.println("Do sth special for Company D");
    
        return c;
      }
    }

     

     

    4. 메소드를 사용한 방식과 비교하여 개선된 점

    1.2.2에서 사용한 메소드를 사용한 관심사 분리는 여러 메소드에서 중복적으로 사용되는 로직을 분리하여, 수정이 필요한 경우에 메소드 1곳만 수정해도 프로그램이 정상적으로 동작하는 효과를 가지고 있었습니다.

     

    하지만 getConnection 이 구현하고 있는 로직이 UserDao에 포함되어있기 때문에, 별도의 connection 로직이 필요한 경우 대응이 불가능한 아쉬운 점도 가지고 있습니다.

     

    예를 들어, 10,000개의 회사가 UserDao를 구매하길 원한다면
    최악의 경우 UserDao를 사용하기 원하는 회사의 숫자만큼 새로운 클래스를 만들어야될 수도 있습니다.
    모든 회사에 아래와 같은 방식으로 판매를 했는데, add 메소드의 로직에 수정사항이 생긴다면?

    10,000의 클래스를 찾아다니면서 하나씩 수정을 해줘야하는 끔찍한 상황이 발생합니다...

    더보기
    // N사를 위한 UserDao
    public class NUserDao {
      public void add(User user) throws SQLException, ClassNotFoundException {
      	...
      }
    
      public User get(String userId) throws SQLException, ClassNotFoundException {
    	...
      }
    
      public void deleteAll() throws ClassNotFoundException, SQLException {
    	...
      }
    
      private Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook", "root", "12345678");
    
        System.out.println("Do sth special for Company N");
    
        return c;
      }
    }
    
    // D사를 위한 UserDao
    public class DUserDao {
      public void add(User user) throws SQLException, ClassNotFoundException {
      	...
      }
    
      public User get(String userId) throws SQLException, ClassNotFoundException {
    	...
      }
    
      public void deleteAll() throws ClassNotFoundException, SQLException {
    	...
      }
    
      private Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/springbook", "root", "12345678");
    
        System.out.println("Do sth special for Company D");
    
        return c;
      }
    }

     

    그래서 이번 챕터에서는 조금 더 수정에 적극적인 방향으로 수정을 의도했고 상속을 사용하여 구현하였습니다.

    이 방식은 10,000개의 회사에 UserDao를 판매하더라도, 개별 회사의 커넥션 방법을 신경쓸 필요가없고 수정사항도 superclass 한곳을 수정하면 되는 장점을 가지고 있습니다.

     

     


    [출처]

    이일민님의 <토비의 스프링3>

    [이미지 출처]

    https://m.yes24.com/Goods/Detail/4020006

     

    ' > 토비의 스프링' 카테고리의 다른 글

    1.4.1 오브젝트 팩토리  (0) 2024.07.08
    1.3.3 관계설정 책임의 분리  (0) 2024.07.07
    1.3.1 클래스의 분리  (0) 2024.07.07
    1.2.2 커넥션 만들기의 추출  (0) 2024.07.07
    1.1 초난감 DAO  (0) 2024.07.07
Designed by Tistory.