Page tree
Skip to end of metadata
Go to start of metadata

목표

  1. UserDaoImpl 클래스에서 중복을 제거한다.
  2. UserDaoImpl 클래스 외에 다른 Dao 클래스가 추가되었을때도 사용가능하도록 JDBCTemplate 클래스로 분리한다.

 

내용

  1. 단위테스트 작성
  2. INSERT와 DELETE 함수의 중복을 제거한다. (https://github.com/hang1416/slipp-user/tree/step2)
  3. SELECT 함수도 INSERT/DELETE 함수와 중복되는 부분을 제거한다. (https://github.com/hang1416/slipp-user/tree/step3)
  4. 변하지 않는 부분을 별도 클래스로 분리한다. (https://github.com/hang1416/slipp-user/tree/step4)
  5. setPreparedStatement, setResultSet 함수를 추상메소드로 정의한다. (https://github.com/hang1416/slipp-user/tree/step5)
  6. 추상메소드를 인터페이스로 분리한다. (https://github.com/hang1416/slipp-user/tree/step6)

 

step1. 단위테스트 작성

  • 중복 제거 후, UserDaoImpl의 정상동작을 확인하기 위해 단위테스트를 작성한다. 
  • (UserDatTest 클래스의 crud함수는 TestUserDaoImpl 클래스를 테스트하기 때문에 UserDaoImpl 클래스를 테스트하기 위해서 crudH2 함수생성)

step2. INSERT 와 DELETE 함수의 중복을 제거한다.

변하는 것과 변하지 않는 것을 찾아보자.

  • 변하는 것 
    • sql 쿼리문, PreparedStatement 실행문, PreparedStatement 파라미터 set, ResultSet 데이터 get하는 부분
  • 변하지 않는것 
    • connection 가져오기, PreparedStatement, connection 닫기, sql문을 실행해서 PreparedStatement로 반환하는 부분

 

변하지 않으면서 반복되고 있는 finally 부분을 메소드로 추출한다.

close 메소드 추출
private void close(Connection con, PreparedStatement pstmt ) throws SQLException {
		if (pstmt != null) {
			pstmt.close();
		}
		if (con != null) {
			con.close();
		}
}

 

 

 insert, findByUserId, delete 함수 로직을 정리해서 중복을 찾아보자

  • insert
    • DB 커넥션을 가져온다.
    • SQL 문을 담아서 Statement 를 만든다. - 파라미터로 넘어온 User를 Statement에 바인딩한다.
    • Statement를 실행한다. executeUpdate();
    • statement와 connection 을 닫아준다.
  • findByUserId
    • DB커넥션을 가져온다.
    • SQL 문을 담아서 Statement 를 만든다. - 파라미터로 넘어온 UserId를 Statement에 바인딩한다.
    • statement를 실행해서 결과 데이터를 ResultSet 으로 받는다.
    • ResultSet으로 User 객체를 만들어서 return 한다. executeQuery()
    • ResultSet, statement와 connection 을 닫아준다.
  • deleteAllUser
    • DB커넥션을 가져온다.
    • SQL 문을 담아서 Statement 를 만든다.
    • Statement를 실행한다. executeUpdate();
    • statement와 connection 을 닫아준다.

겹치는 부분이 많아보이는 insert 와 deleteAllUser 함수의 로직을 메소드로 추출해보자.

 

executeUpdate 메소드 추출
public void insert(User user) throws SQLException, PropertyVetoException {
		String sql = "INSERT INTO USERS VALUES (?, ?, ?, ?)";
		executeUpdate(user,sql);
}
public void deleteAllUser() throws SQLException, PropertyVetoException {
		String sql = "DELETE FROM USERS";
		executeUpdate(null,sql);
		
}
private void executeUpdate(User user, String sql) throws SQLException,
			PropertyVetoException {
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			pstmt = con.prepareStatement(sql);
			if(user !=null){
				pstmt.setString(1, user.getUserId());
				pstmt.setString(2, user.getPassword());
				pstmt.setString(3, user.getName());
				pstmt.setString(4, user.getEmail());
			}
			pstmt.executeUpdate();
		} finally {
			close(con,pstmt);
		}
}

 

findByUserId 함수에는 위 2개의 함수와 비교했을 때, 다른 부분이 더 존재한다.
  • UserId 파라미터 하나만 Statement에 바인딩한다. (insert 는 파라미터 4개를 바인딩한다.)
  • DB 질의문 실행 후, 데이터를 리턴받아서 User 객체로 매핑한다.

findByUserId 함수에서도 executeUpdate 함수를 사용하려면 어떻게 해야할까?

  • executeUpdate 함수에 파라미터를 하나 추가해서 select 쿼리여부에 따라 분기문을 만들면.. executeUpdate 함수가 너무 지저분할 것 같다.
  • 재성오빠가 보내준 참고 url을 보기로 함. 음.. 이상황에서 보기는 좀 아쉬우니까 setp3 까지 진행해보자

step3. SELECT 함수도 INSERT/DELETE 함수와 중복되는 부분을 제거한다.

  • executeUpdate 함수명을 execute 로 변경한다. (executeUpdate 뿐 아니라 executeQuery도 포함하기 위해)
  • execute 함수에 queryType 파라미터를 추가해서 insert, select, delete 별 로직을 실행할 수 있도록 한다.
  • execute 함수가 너무 많은 일을 하니까 PreparedStatement 에 set 하는 부분과 ResultSet에 set 하는 부분을 메소드로 추출한다.
  • queryType 구분의 가독성을 위해, 또 변하지 않는 값이기 때문에 static final 변수로 선언한다.
execute 함수 정리
        private  static final String QUERY_TYPE_INSET ="INSERT";
	private  static final String QUERY_TYPE_DELETE ="DELETE";
	private  static final String QUERY_TYPE_SELECT ="SELECT";
	private User execute(User user,String sql, String queryType) throws SQLException,
			PropertyVetoException {
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = connectionManager.getConnection();
			
			pstmt = con.prepareStatement(sql);
			if(!QUERY_TYPE_DELETE.equals(queryType)){
				setPreparedStatement(user, pstmt, queryType);
			}
			if(QUERY_TYPE_SELECT.equals(queryType)){
				rs = pstmt.executeQuery();
				return setResultSet(rs);
			}else{
				pstmt.executeUpdate();
				return null;
			}
		} finally {
			close(con, pstmt);
		}
	}
	private User setResultSet(ResultSet rs) throws SQLException {
		User rsUser = null;
		if (rs.next()) {
			rsUser = new User(
					rs.getString("userId"), 
					rs.getString("password"), 
					rs.getString("name"),
					rs.getString("email"));
		}
		return rsUser;
	}
	private void setPreparedStatement(User user, PreparedStatement pstmt, String queryType)
			throws SQLException {		
		
		pstmt.setString(1, user.getUserId());
		if(QUERY_TYPE_INSET.equals(queryType)){
			pstmt.setString(2, user.getPassword());
			pstmt.setString(3, user.getName());
			pstmt.setString(4, user.getEmail());
		}
	}

 

중복은 제거했지만, 다른 쿼리들이 추가된다면 execute 함수는 계속 변경되어야 한다.

즉, 확장에 열려있는 코드라고 할 수 없다.

 

step6. 인터페이스 분리

delete 함수에서는 PreparedStatement 파라미터를 사용하지 않는데도 불구하고,  setValue 함수를 구현하고 있다. 구현하지 않을 방법이 있을까?

  • No labels

2 Comments

  1. 혜영이 마지막까지 화이팅... JDBC API에서 중복 코드 제거하는 부분을 완전히 이해하게 되면 Spring의 Template을 끝나는 대부분의 API 구조를 이해할 수 있을거야. Spring API들은 일관적이거든.

    어쩌면 네가 원하는 API들을 이 구조로도 프레임워크화할 수 있을거야. 정말 중요한 부분이고 네가 바로 사용할 수 있는 내용들이 담겨 있으니 끝까지 달려보길 바란다. 내일 보자.

  2. 추상메서드까지만 하고 접을려고 했는데... 재성오빠 댓글을 봐버렸어요 ㅎㅎ 인터페이스까지 달려볼께욧! 쿠오오오~