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

배지원·2022년 10월 23일
0

실습

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

이전에 했던 프로젝트를 Delete와 getCount를 구현한 후 예외처리와 Spring TDD를 통해 리팩토링 해보겠다.
이전 프로젝트 : https://velog.io/@qowl880/Java-Java%EC%97%90%EC%84%9C-Query%EB%AC%B8-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%813

1. 코드 추가

  • Delete와 getCount를 추가해보도록 하겠다.

(1) Delete( )

  • 데이터를 삭제하는 쿼리
    public void deleteAll() throws SQLException, ClassNotFoundException {       // DB 모든값 삭제
        Connection conn = null;
        PreparedStatement ps = null;
       
        try {                   // 예외처리
            conn = connectionMaker.makeConnection();
            ps = conn.prepareStatement("delete from user ");
            ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {      // error 발생해도 실행함
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                }
            }

            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                }
            }
        }
    }
  • DB내의 데이터를 모두 삭제할 수 있도록 Delete를 사용하는 메서드를 구현하였다.
  • 이때 모든 데이터를 삭제할 때는 TRUNCATE 명령어를 사용해도 된다. 간단하게 TRUNCATE는 테이블 전체 데이터를 삭제하는 것이고 Delete는 조건을 통한 1개~전체를 삭제할 수 있다.
  • try-catch문으로 예외 처리했는데 이 내용은 마지막에 다루겠다.

(2) Count( )

  • 데이터의 개수를 가져오는 쿼리
public int getCount() throws SQLException, ClassNotFoundException {     // user테이블의 레코드 개수를 구하기
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = connectionMaker.makeConnection();
            ps = conn.prepareStatement("select count(*) from users ");
            rs = ps.executeQuery();
            rs.next();
            return rs.getInt(1);

        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            if(rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                }
            }
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                }
            }

            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                }
            }
        }
    }
  • select count를 통해 현재 테이블안에 있는 데이터의 개수를 출력해준다.

2. 예외 처리

Try, Catch, Finally

  1. Try : 실행할 코드 영역, 예외 발생이 가능한 부분 예외 발생시 catch로
  2. Catch : 예외 처리 코드 영역
  3. finally : 예외와 상관없이 마지막에 필수적으로 실행되는 코드 영역

이를 토대로 모든 동작하는 메서드에 try-catch문을 통해 예외처리를 해보겠다.

DAO 클래스

public class UserDao {

    private ConnectionMaker connectionMaker;    // interface의 makeConnection()를 가져옴
    public UserDao(){                  // 생성자를 통해 AWS DB의 makeConnection()을 오버라이딩하여 사용
        this.connectionMaker = new AWSConnectionMaker();
    }
    public  UserDao(ConnectionMaker connectionMaker){
        this.connectionMaker = connectionMaker;
    }


    public void deleteAll() throws SQLException, ClassNotFoundException {       // DB 모든값 삭제
        Connection conn = null;
        PreparedStatement ps = null;
       
        try {                   // 예외처리
            conn = connectionMaker.makeConnection();
            ps = conn.prepareStatement("delete from user ");
            ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {      // error 발생해도 실행함
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                }
            }

            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    public int getCount() throws SQLException, ClassNotFoundException {     // user테이블의 레코드 개수를 구하기
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = connectionMaker.makeConnection();
            ps = conn.prepareStatement("select count(*) from users ");
            rs = ps.executeQuery();
            rs.next();
            return rs.getInt(1);

        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            if(rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                }
            }
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                }
            }

            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    public void add(User user) throws ClassNotFoundException, SQLException {
   // db 연결(호스트,이름,비밀번호)
        Connection conn = null;     // 설정들을 모아둔 메서드 호출
        PreparedStatement ps = null;
        try {
            conn = connectionMaker.makeConnection();

            ps = conn.prepareStatement("INSERT INTO users(id,name,password) VALUES(?,?,?)");
            ps.setString(1,user.getId());        // mysql 테이블로 값 insert
            ps.setString(2,user.getName());
            ps.setString(3,user.getPassword());
            ps.executeUpdate();     // ctrl + enter 즉, mysql에서 번개모양을 눌러 최신화 한다는 느낌
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                }
            }

            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                }
            }
            System.out.println("데이터가 insert 됬습니다.");
        }
    }

    public User select(String id) throws SQLException, ClassNotFoundException {

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;   // 쿼리문을 저장함 insert문과 달리 excuteQuery() 사용
        User user = null;
        try {
            conn = connectionMaker.makeConnection();
            ps = conn.prepareStatement("SELECT id,name,password FROM users WHERE id = ?");
            ps.setString(1, id);     // id는 get(String id)로 받은 id
            rs = ps.executeQuery();
            // rs에는 쿼리 실행 결과가 담겨져 있다. (select * from users where id = 1;)
            rs.next();
            // User 생성자를 통해 쿼리문에 id값을 넣어 찾은 id, name,password 값을 저장한다.
            return new User(rs.getString("id"), rs.getString("name"), rs.getString("password"));
        }
        catch (SQLException e) {
            throw new RuntimeException(e);
        }
        finally {
            if(rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                }
            }
            if(ps != null){
                try {
                    ps.close();
                } catch (SQLException e) {
                }
            }

            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                }
            }
        }
    }
}
  • DAO 전체 클래스이다.
  • 먼저, 각 리소스를 null로 초기화하고, 해당 리소스의 상태에 따라 에러를 실행시키는 방식이다. 이때, 리소스를 정상적으로 반환하지 못하는 경우 서버가 다운되는 치명적인 상황이 발생할 수 있기 때문에, 공유 리소스를 사용하는 경우 예외처리를 잘 해줘야 한다.

3. Spring TDD

Spring Test에 관한 내용은 다음 블로그에 정리가 되어 있으니 참고해주길 바란다.
https://velog.io/@qowl880/test

TDD

@ExtendWith(SpringExtension.class)  // spring에서 테스트 하기 위한 설정
@ContextConfiguration(classes = UserDaoFactory.class)
class UserDaoFactoryTest {
                    // 싱글톤을 위해 Autowired 사용
    @Autowired      // 새로운 객체를 사용하지 않고 이전에 사용했던 객체의 주소를 그대로 사용한다는 설정
                    // new 객체 생성을 한번만 사용함(고정값)
    ApplicationContext context; // Spring ApplicationContext를 사용하기 위해서는
    // @ExtendWith 과 @ContextConfiguration를 추가해줘야 한다.
    UserDao userDao;
    User user1;
    User user2;
    User user3;
    
    @BeforeEach
    void setUp(){       // 각각의 테스트 시작전 필수로 동작하는 부분
        userDao = context.getBean("awsUserDao", UserDao.class);
        user1 = new User("1","홍길동","1234");
        user2 = new User("2","이순신","4567");
        user3 = new User("3","세종","7896");
    }
    @Test
    void addAndSelect() throws ClassNotFoundException, SQLException {

        // .deleteAll() 오류 검증
        userDao.deleteAll();
        Assertions.assertThat(0).isEqualTo(userDao.getCount());

        String id = "3";
        userDao.add(user3);
        Assertions.assertThat(1).isEqualTo(userDao.getCount());

        User user = userDao.select(id);

        Assertions.assertThat("세종").isEqualTo(user.getName());
   //     assertEquals("Spring",user.getName());
    }
    @Test
        //.count test만들기
    void count() throws SQLException, ClassNotFoundException {

        UserDao userDao = context.getBean("awsUserDao", UserDao.class);
        //.deleteAll() 오류 검증
        userDao.deleteAll();
        assertEquals(0, userDao.getCount());

        userDao.add(user1);
        assertEquals(1, userDao.getCount());
        userDao.add(user2);
        assertEquals(2, userDao.getCount());
        userDao.add(user3);
        assertEquals(3, userDao.getCount());
    }
}
  • spring에서 테스트하기 위해 @ExtendWith를 설정한다.
  • ApplicationContext를 사용하기 위해 @ContextConfiguration를 추가해주고 Bean파일을 등록한다.
  • @Autowired를 선언하여 Bean파일의 값이 자동으로 입력된다.
  • @BeforeEach를 사용하여 각 TEST가 실행되기 전에 먼저 실행되어 값을 입력할수 있도록 한다. 이때, bean파일에 원하는 빈을 가져오기 이해 getBean을 사용한다.
  • @TEST를 통해 각 부분별 테스트를 진행한다.

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

profile
Web Developer
post-custom-banner

0개의 댓글