시소당
[EJB 기반 프로젝트 수행 가이드] ① |
세션빈에서의 DB 접근전략 및 엔터티빈 사용시 주의사항 |
|
꼭 PreparedStatement를 사용하자 Statement 인터페이스 보다는 PreparedStatement가 낫다. 사실 ‘좋다’ 정도가 아니라 ‘Statement를 쓰지 말고 PreparedStatement를 써야한다’고 말해도 될 정도다.
getEmpName의 메소드에서 PreparedStatement 대신 Statement의 executeQuery(String sql) 메소드를 사용해서 개발하면 어떤 일이 생기는지 알아보자.
인
사업무 담당자가 사원 한 명씩 상세정보를 조회하고자 한다고 가정하자. 조회버튼을 누르면 사원번호로 getEmpName
메소드안에서 쿼리가 스트링으로 완성돼 Statement의 executeQuery(String sql) 메소드안의 SQL 값으로
들어간다. DB의 옵티마이저가 쿼리를 파싱, 실행하고, resultSet 리턴한다.
여기서 다음 사원을 조회하면? 사원번호만 다른, 그러나 방금전 조회시의 쿼리와 모두 같은 스트링이 던져진다. DB의 옵티마이저가 쿼리를 파싱, 실행한다.
계속 다른 사원을 조회하면? DB 입장에서는 조회조건만 다른 쿼리에 대해 파싱을 반복적으로 하고 있는 셈이고, 매번 파싱을 하고 결과값을 리턴해야 하기에 조회시 결과값 출력도 그만큼 늦어진다.
만
일 이 시스템 전체가 Statement 인터페이스로 이뤄져 있고, 향후 시스템 성능 테스트에서도 이 부분이 발견되지 않는다면
어느날 갑자기 원인도 모르게 CPU 사용도가 올라가고 시스템 전체가 느려지는 현상이 발생하게 된다. 서버 관리자가 서버를 내렸다
올리겠지만 원인을 알 수 없으니 속이 편하진 않을 터.
나중에 다시 스트레스 테스트를 하고, 결국 파싱과 실행 수가 비슷하다는 사실이 발견된다. 책임은 개발자들에게 돌아간다.
Statement
로 이뤄진 프로그램을 PreparedStatement로 바꾸라는 지시가 떨어지겠지만 이는 간단치 않은 일이다.
Statement로 되어있는 프로그램을 그동안 copy & paste 해서 만든 프로그램이 몇 개인지 파악도 안되고,
파악이 되다 해도 바꾸는 작업은 쉽지 않다.
PreparedStatement는 주어진 쿼리를 미리 파싱을 해두기 때문에, 조회조건만 달라진 다른 쿼리가 들어오면 바로 실행만 시키게 된다. 시스템 성능 면에서 훨씬 더 효율적이다.
엔터티빈으로 접근한 row는 또 다른 Connetion 객체로 접근하지 말 것 세
션빈에서 한 레코드에 대해 변경을 하는 작업을 엔터티빈으로 한다고 하고, 이때 요청사항으로 추가되는 어떤 컬럼에 대해 변경하는
부분이 늘어난다고 하자. 기존 소스는 아래와 같다. 아래 소스는 세션빈 내에서 사원발령이 났을 경우 발령 시작일자와 발령
마감일자를 변경하는 비즈니스 프로세스를 갖고 있다.
public boolean setMoveEmp(String empNo,String startDt, String endDt ) throws Exception {
boolean doneYn = false; EmpHome empHome = null; Emp emp = null;
try { EmpPk pk = new EmpPk();
/* HomeFactory : JNDI 이름으로 Entitybean의 HOME 객체를 리턴한다 */ empHome = (EmpHome)HomeFactory.getHome(EmpHome.클래스);
pk.empNo = empNo; emp = empHome.finDByPrimaryKey(pk); emp.setStartDt( startDt ); emp.setEndDt( endDt );
} catch(Exception e) { throw new Exception(e.toString()); } finally { ConnManager.closeConnection(con); } catch(Exception e) { throw new Exception(e); } } return doneYn; }
| |
여
기에서 사원발령정보를 변경하기 위해 엔터티빈 리모트 인터페이스의 setStartDt, setEndDt 메소드를 사용했다. 여기서
만일 사원발령정보를 낼 때 변경 일자도 기록하라는 요청사항이 추가될 경우, 이를 기존 소스에 추가하는 작업에서 엔터티빈을
사용하지 않고 Connection 객체를 만들어서 직접 SQL문을 던지는 형식으로 레코드의 한 컬럼 값을 변경시킨다고
가정해보자.
public boolean setMoveEmp(String empNo,String startDt, String endDt ) throws Exception {
/* Connection 선언 추가한다. */ Connection con = null; boolean doneYn = false; EmpHome empHome = null; Emp emp = null;
try {
/* Connection instance 받아온다. */ con = ConnManager.getConnection(); EmpDB DB = new EmpDB(); /* DB 래퍼 선언 */
EmpPk pk = new EmpPk();
/* HomeFactory : JNDI 이름으로 Entitybean의 HOME 객체를 리턴한다 */ empHome = (EmpHome)HomeFactory.getHome(EmpHome.클래스);
pk.empNo = empNo; emp = empHome.finDByPrimaryKey(pk);
emp.setStartDt( startDt );
/* 변경 일자를 기록하는 부분이 추가된다 */ DB.setUpdateDate(con,empNo);
emp.setEndDt( endDt );
} catch(Exception e) { throw new Exception(e.toString()); } finally { try { ConnManager.closeConnection(con); } catch(Exception e) { throw new Exception(e); } } return doneYn; }
| |
위의 소스를 보면 일견 아무런 문제가 없어 보인다. 컴파일도 잘 되고 실행시에 아무런 에러도 나지 않는다. 하지만 결과를 보면,
기존
emp.setStartDt( startDt ); emp.setEndDt( endDt );
| |
를 타서, 레코드에 startDt와 endDt 컬럼 부분은 모두 원하는 데로 변경이 되었으나,
/* 변경 일자를 기록하는 부분이 추가된다 */ DB.setUpdateDate(con,empNo);
| |
부분이 실행이 되었음에도 변경 일자 컬럼에는 아무런 변화가 없음을 알 수 있다.
엔
터티빈의 인스턴스를 가지고 온 후부터 DB 레코드와 엔터티빈간의 동기화가 형성되고, 세션빈 메소드가 끝났을 때 DB에 결과를
쓰게 된다. 즉, 최종적으로 기록이 남는 것은 엔터티빈으로 작업한 결과이다. 중간에 새로 생긴 업데이트 세션은 그 작업은
성공한다 해도 DB에는 전혀 반영이 되지 않는 것이다.
이 소스를 보고 ‘그렇다면 모두 엔터티빈 메소드로
실행시키면 되지 왜 굳이 Connection 객체로 접근하는가'라는 의문이 들 수도 있다. 하지만 시스템을 오픈하고 나서
테이블에 컬럼을 추가해야 하는 경우가 업무적으로 반드시 생기게 된다. 그때 추가된 컬럼에 대해서는 엔터티빈안에 추가된 컬럼의
필드와 상응하는 변수를 추가하고 엔터티빈을 서버에 다시 배치해야 한다. 하지만 이 작업이 번거로우므로 추가된 컬럼에 한해서
SQL 쿼리로 간단히 핸들링하고자 하는 계획을 세우게 된다. 그럴때 위와 같은 경우가 생기게 되고, 한 row에 대해서
엔터티빈을 콜 할 때 엔터티빈 인스턴스와 DB가 동기화 된다는 것을 염두에 두지 못한 채 Connetion 객체를 써서
업데이트하는 하는 일도 생기는 것이다.
위의 소스는 모두 Connection 객체를 써서 해당 row의 컬럼을 변경 시키든지, 아니면 모두 엔터티빈 메소드를 쓰도록 변경해야 한다.
엔터티빈 사용을 지양하자 주위에서 진행되는 프로젝트들을 살펴보면 엔터티빈을 몰라서 쓰지않는 것이 아니라, 엔터티빈의 성질을 잘 알기 때문에 안쓰는 경우를 종종 보게된다.
엔터티빈의 이점이 몇가지 있다는 것은 분명하지만 개발자 입장에서는 쿼리로 간단히 대처를 할 수 있다는 측면에서 엔터티빈을 기피하게 된다.
한
편 엔터티빈 말고 Connection 객체로 DB를 핸들링한다고 해서 트랜잭션이 언제나 완결되는가 하면 그것은 또 아니다. 분명
엔터티빈보다는 훨씬 더 강력하지만 예외도 있다. 다음 이어지는 강좌에서는 그에 해당하는 예제를 들고, 이를 피하려면 어떤 전략을
취해야 하는지 소개하도록 하겠다. @