[Spring] Spring에서 Query문 리팩토링(3)

배지원·2022년 10월 26일
0

실습

목록 보기
7/24
post-custom-banner

이전에 했던 프로젝트를 DataSource, 익명클래스로 리팩토링하여 기존의 코드를 좀 더 간단히 출력하고 중복코드를 줄일 수 있도록 함
이전 프로젝트 : https://velog.io/@qowl880/Spring-Spring%EC%97%90%EC%84%9C-Query%EB%AC%B8-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%812

1. DataSource

  • 이전에 사용한 ConnectionMaker는 단순히 DB커넥션을 생성해주는 기능 하나만을 가진 인터페이스라면, 이미 DB 커넥션을 가져오는 오브젝트의 기능을 추상화 해서 사용할 수 있게 만들어진 DataSource 인터페이스가 이미 존재한다.
  • 따라서 따로 인터페이스를 구현하지 않고도 구현이 가능하다.

Factory

  • Factory(Bean) 폴더에 작성
@Bean
    public UserDao awsUserDao() {
        UserDao userDao = new UserDao(awsdataSource());
        return userDao;
    }

@Bean
    DataSource awsdataSource() {
        Map<String, String> env = System.getenv();
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriverClass(com.mysql.cj.jdbc.Driver.class);
        dataSource.setUrl(env.get("DB_HOST"));
        dataSource.setUsername(env.get("DB_USER"));
        dataSource.setPassword(env.get("DB_PASSWORD"));
        return dataSource;
    }

DAO

public class UserDao {
private DataSource dataSource;//ConnectionMaker connectionMaker;

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

public User select(String id) throws SQLException {
        Connection c = dataSource.getConnection();//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(rs.getString("id"), rs.getString("name"), rs.getString("password"));


        rs.close();
        ps.close();
        c.close();

        return user;
    }//기존 ConnectionMaker부분을 dataSource로 의존관계를 바꾼다
}

2. 익명클래스

  • 이름을 갖지 않는 클래스로 클래스 선언과 오브젝트 생성이 결합된 형태로 만들어지며, 상속할 클래스나 구현할 인터페이스를 생성자 대신 사용해서 다음과 같은 형태로 만들어 사용한다.
new 인터페이스이름() {클래스 본문};
  • 익명 내부 클래스를 도입한 이유: StatementStrategy Interface의 구현체인 DeleteAllStrategy()를 쓰는 곳이 deleteAll()한군데 뿐이기 때문에 굳이 class를 새로 만들 필요가 없다. 메소드도 한 개 뿐이라서 더욱 그렇다.
  • AddStatement도 마찬가지로 익명 내부 클래스로 만들 수 있다.
  • 아래 코드를 보면 jdbcContextWithStatementStrategy(new StatementStrategy() {}를 이용해 익명클래스를 구현했다.
public void deleteAll() throws SQLException {
   // "delete from users"
   jdbcContextWithStatementStrategy(new StatementStrategy() {
       @Override
       public PreparedStatement makeStatement(Connection conn) throws SQLException {
           return conn.prepa reStatement("delete from users");
       }
   });
}

public void add(final User user) throws SQLException {
   // DB접속 (ex sql workbeanch실행)
   jdbcContextWithStatementStrategy(new StatementStrategy() {
       @Override
       public PreparedStatement makeStatement(Connection conn) throws SQLException {
           PreparedStatement pstmt = null;
           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());
           return pstmt;
       }
   });
}

1. JdbcContext 분리

  • 중복되지 않는 코드는 클린코드이다. 따라서 최대한 중복코드는 제거해 주는것이 좋고 또한 jdbcContextWithStatementStrategy는 어디 하나에 종속되지 않고 Dao에서도 사용이 가능하므로 UserDao에서 분리한다.
    (ex. UserDao뿐만 아니라 HospitalDao등에서도 사용하기 위해)
    JdbcContext 클래스를 새로 만든다
public class JdbcContext {

   private DataSource dataSource;

   public JdbcContext(DataSource dataSource) {
       this.dataSource = dataSource;
   }

public void workWithStatementStrategy(StatementStrategy stmt) throws SQLException {
   Connection c = null;
   PreparedStatement pstmt = null;
   try {
       c = dataSource.getConnection();
       pstmt = stmt.makeStatement(c);
       // Query문 실행
       pstmt.executeUpdate();
   } catch (SQLException e) {
       throw e;
   } finally {
       if (pstmt != null) {
           try {
               pstmt.close();
           } catch (SQLException e) {
           }
       }
       if (c != null) {
           try {
               c.close();
           } catch (SQLException e) {
           }
       }
     }
   }
}

2. UserDao가 JdbcContext를 의존하게 변경

add메서드만 구현(Delete도 똑같이 구현하면 됨)

public class UserDao {

   private final DataSource dataSource;
   private final JdbcContext jdbcContext;

   public UserDao(DataSource dataSource) {
       this.dataSource = dataSource;
       this.jdbcContext = new JdbcContext(dataSource);
   }

   public void add(final User user) throws SQLException {
       // DB접속 (ex sql workbeanch실행)
       jdbcContext.workWithStatementStrategy(new StatementStrategy() {
           @Override
           public PreparedStatement makeStatement(Connection conn) throws SQLException {
               PreparedStatement pstmt = null;
               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());
               return pstmt;
           }
       });
   }

3. TemplateCallback 적용

  • 현재 Delete와 Add메서드의 익명클래스가 중복코드가 발생한다. 따라서 이를 방지하기 위해 메서드로 만들어 구현할 수 있도록 한다.

JdbcContext파일에 넣음

public void executeSql(String sql) throws SQLException {
    this.workWithStatementStrategy(new StatementStrategy() {
        @Override
        public PreparedStatement makePreparedStatement(Connection connection) throws SQLException {
            return connection.prepareStatement(sql);
        }
    });
}

DAO(Delete)

  • Dao에서는 sql쿼리문만 넣어 넘겨주면 된다.
public void deleteAll() throws SQLException {
    this.jdbcContext.executeSql("delete from users");
}

※ 코드 정리 : https://github.com/Bae-Ji-Won/Spring-Study/tree/main/git/Toby-spring-jdbc/src/main/java/dao/Datasource

profile
Web Developer
post-custom-banner

0개의 댓글