본문 바로가기

Dev.../플밍 관련 자료

[펌] [EJB 기반 프로젝트 수행 가이드] ②

세션빈에서의 트랜잭션 관리
김주현 (ERP 개발자)
2004/03/30
① 세션빈에서의 DB 접근전략 및 엔터티빈 사용시 주의사항
② 세션빈에서의 트랜잭션 관리
③ 비즈니스 프로세스 구현 최적화하기
④ 능률 높여주는 유틸리티를 사용하자

지난 강좌에서는 세션빈 내에서 DB를 핸들링하는 데에는 JDBC 드라이버를 이용한 Connection 객체를 사용하는 방법과, 엔터티빈을 이용하는 방법이 있다고 언급했다. 중요한 점은 두가지 방법을 혼용하는 것이 아니라 엔터티빈 사용시 발생하게 되는 문제점을 미리 알고 엔터티빈 사용을 지양하자는 것이다.

지난번 강좌에 이어 엔터티빈 사용을 지양할 수 밖에 없는 사례 한 가지를 더 예로 들겠다.

통상 SI 프로젝트에서 인사시스템과 같이 개인의 정보보안이 엄격히 다루어져야 하는 성격의 시스템인 경우, 업무 전반 모두를 웹기반으로 구현하지는 않는다. 물론 웹으로 구현하는 것과 보안과는 별개의 문제이지만, 아직도 공기업 등에서는 웹기반 구현을 꺼리는 경우가 많기 때문이다.

급여계산 및 업무평가점수계산 등 민감한 성격의 정보를 다루는 프로그램은
클라이언트/서버(C/S) 환경으로 구축돼 업무 담당자만 사용 할 수 있도록 하고, 웹으로는 일반사원들이 자신의 정보를 확인하는 조회성 프로그램만으로 구성되도록 요청하는 사례가 많다.

조회성 기능만 갖고 있는 웹기반 프로그램을 설계한다면, 설계자는 DB의 레코드를 SELECT하고 화면에 보여주는 기능만을 수행하므로 쿼리보다는 엔터티빈을 활용하겠다는 기준을 세우고, 실제 개발도 엔터티빈을 이용해 진행하게 된다.

C/S 환경 시스템과 웹프로그램은 서로 업무 범위 및 구축된 환경만 다를 뿐이지 물리적으로 같은 DB를 사용하고 있다. 이러한 환경에서 간혹 발생하는(주로 동시사용자수가 증가할 때 발생한다) EJB 컨테이너의 ‘NoSuchObjectException’ 장애물을 알아둬야 한다.

C/S 프로그램으로 업무를 하는 담당자와 일반 사용자가 같은 레코드를 조회한 상태에서, 담당자가 해당 레코드를 수정 혹은 삭제했고, 웹의 클라이언트 모듈이 해당 레코드를 표현하는 엔터티빈을 계속 참조하고 있는 상황이라면 NoSuchObjectException을 보게 될 것이다.

이 Exception을 던지는 일은 EJB 컨테이너의 기능 중 하나로 엔터티빈의 객체정보와 실제 DB의 정보가 일치하지 않을 경우 동작하게 된다. EJB 컨테이너는 실제 DB와 엔터티빈 객체를 동기화하고 있기 때문에 다른 세션이 자신이 참조하고 있는 DB의 레코드를 변경했을 경우 Exception을 던지는 것이다.

이 Exception이 발생하면 이를 해결하는 방법은 컨테이너가 참조하고 있는 엔터티빈을 핫디플로이 시켜서 객체를 다시 참조케 하거나, 핫디플로이 방법을 쓸 수 없다면 EJB 서버를 RESTART하는 수 밖에 없다.

컨테이너가 NosuchObjectException을 던질 경우 자동으로 엔터티빈을 Passivate시키고 Activate시키면 편리하겠지만 아쉽게도 그런 기능은 없다. 개발자가 향후에 NosuchObjectException이 생기지 않도록 프로그램마다 빈의 라이프싸이클을 제어하는 부분을 추가하는 방안도 생각해 볼 수 있겠지만 그 작업은 시간 소모가 크다.

해당 엔터티빈의 실행 중 정보를 갖고 있는 XML 데이터의 값 중에서 이러한 에러를 일으키지 않도록 해주는 속성이 무엇인지 안다면, XML 데이터의 값을 고치고 다시 디플로이 하는 방법도 있겠다.

하지만 개발자 입장에서는 본인의 판단 착오와는 전혀 관련이 없는 에러를 맞닥뜨리게 된 것이며, 이미 에러를 발견한 이후에 작업을 하는 것이므로 이미 사용자의 프로그램에 대한 신뢰도가 떨어져있는 상황이겠다. 또한 시스템 내의 모든 엔터티빈을 찾아내 작업을 하기도 꽤 번거로운 일일 것이다.

컨테이너의 동기화 기능이 유발시키는 NoSuchObjectException과 엔터티빈과 매핑되는 테이블이 컬럼이 추가되는 등의 변경 사항이 있을 시 엔터티빈의 소스를 고쳐 다시 디플로이 해야 한다는 문제점으로 인해, 항상 시간에 쫓기는 프로젝트 개발자 입장에서는 엔터티빈의 스펙때문에 자신의 보폭이 좁아지는 느낌을 받을 것이다
.
그리하여, 기존 시스템은 엔터티빈을 이용하되 추가 프로젝트에서는 엔터티빈을 전혀 사용하지 않고, DB핸들링은 100% SQL문으로 처리하고 있는 싸이트도 생기게 된다.

엔터티빈을 쓰지 않으면 DB의 테이블마다 SELECT, DELETE, INSERT, UPDATE 등의 쿼리를 일일이 개발자가 생산해야 하지 않느냐는 의견도 나올 수 있으나 프로젝트 프로토타이핑 단계시점 등에 테이블당 쿼리문을 자동으로 생성시켜주는 간단한 유틸 프로그램을 직접 만들어 사용한다면 생산성이 비약적으로 높아질 수 있다.(4회 강의에서 이 유틸을 소개할 예정이다.)

엔터티빈 사용시 발생하게 되는 문제점에 대한 이야기는 여기서 마무리하고, 초점을 세션빈으로 돌리자. 세션빈 내에서 비즈니스 처리를 할 때 유의해야 할 점이 무엇인지 알아본다.

비즈니스 프로세스와 트랜잭션
하나의 비즈니스 프로세스가 처리될 때 가장 중요하게 지켜져야 할 사항은
트랜잭션이 결함없이 처리되어야 한다는 점이다. 예를 들어 보험금 수납시에 보험담당자의 실적이 체크됨과 동시에, 회계장부에 보험금이 기록되어야 하는 [보험금수납]이란 이름의 업무정의가 있을 경우, 이 업무가 성공적으로 완료되었다고 판단할 수 있는 것은 단 2가지 외에는 없다.

실적도 체크되지 않고 장부에도 기록되지 않은 경우와, 실적 체크와 장부 기록 둘 다 성공한 경우다. 이 외 둘 중 하나만 성공한 경우는 트랜잭션이 실패한 것이다. 시스템 운영상 결코 발생해서는 안되는 치명적인 결함이다.

세션빈이 수행하는 메소드안에서 반드시 트랜잭션이 성공해야 함은 아무리 강조해도 지나치치 않을 것이다.


EJB 컨테이너와 트랜잭션
EJB 개발자들은 세션빈의 메소드 단위로 트랜잭션 레벨을 설정할 수 있다는 것을 알고 있다. 또한 컨테이너가 개발자가 설정한 트랜잭션의 레벨에 따라 메소드 내의 트랜잭션을 하나로 묶어준다는 것도 알고 있다.

EJB의 실행중 속성정보를 갖고 있는 XML 파일안을 조작함으로써 트랜잭션 관리가 필요한 메소드를 지정할 수 있고(기본값은 ALL이다), 레벨 또한 조작할 수 있다.
설정 레벨은 REQUIRED로 지정한다(트랜잭션을 관리할 필요가 없는 업무정의가 존재하는 경우는 없다고 봐도 좋다).

컨테이너는 이 두 가지 정보를 가지고 메소드 시작시점부터 완료시점까지 Exception이 발생하게 되면 레벨이 REQUIRED일 경우 Exception발생 전까지 수행했던 작업을 모두 롤백시킨다.

설정레벨을 하위로 조정하면 Exception을 던지되 롤백시키지는 않는 현상이 벌어진다. 그러므로 이 XML의 기본값을 고치는 경우는 없고, 고쳐서도 안된다. VisualCafe와 같이 자동으로 EJB의 XML을 생성시켜주는 툴에서부터 모두 이 옵션을 기본적으로 REQUIRED로 설정하고 있다.

이렇게 트랜잭션을 컨테이너가 메소드 단위로 관리하므로 개발자 입장에서는 명시적으로 트랜잭션을 ON/OFF 하는 기능을 구현할 필요 없이 일정부분 태스크가 줄어든 상태로 볼 수 있다.

하지만 메소드 내에서 Connection을 씀에 있어 언제 Connection을 얻어오고 반환해야 할 지, 또는 Connection 객체를 몇 개 선언해 생성시켜 쓸 것인지와 같은 고민들은 컨테이너의 기능과 별개로 전체 프로젝트에 일관성 있게 적용되어야 할 표준으로 정해져야 하며, 이는 설계자와 개발자의 몫이다.

Connection 객체 핸들링을 하는 기준
Conection 객체를 어느 클래스가 생성하며 반환할 것인가, DB 래퍼 클래스의 메소드와 통신할시 Connection 객체를 넘길 것인가 말 것인가, 넘긴다면 어떤 방법으로 넘기는가에 대한 기준을 세워야 한다.

세션빈 Connection을 선언하고 생성시켜 얻어오는 주체는 1회 강의에도 말했듯이 세션빈 클래스이다. 세션빈 클래스 안에서 Connection 객체는 전역변수로 선언하지 말아야 한다.

각각의 메소드마다 지역변수로 선언하여 사용하는 것이 효과적이다. 또한 메소드안의 프로세스가 진행 완료됐을 경우, finally 절에서 반드시 Connection을 반환해야 하는 것도 중요한 원칙 중 하나다. 커넥션을 얻어올 때, SQLException을 던져야 하므로 try, catch문을 사용하게 되는데, finally 절에서 반드시 커넥션을 반환하도록 하자.

생성된 Connection 객체는 DB와의 작업을 모두 한 이후 반드시 close 메소드를 통해 닫혀져야 하는데, Connection 객체가 반환되지 않는 경우 심각한 시스템 자원낭비를 유발하게 된다. 이는 곧 프로그램의 운용성능 저하로 이어진다.

Connection 객체생성은 언제나 close() 메소드와 쌍을 이루어야 한다는 식의 사고습관을 가져야 하겠다.

다음으로 언급할 것이 DB 래퍼 클래스와의 통신 패턴이다. DB 래퍼 클래스는 DB에 SELECT, INSERT, DELETE, UPDATE 쿼리를 전달하는 역할을 하는 메소드 집합체로써, DB와 가장 가까운 층이다.

세션빈 메소드는 복잡한 비즈니스 프로세스를 구현하게 되는데, 메소드 시작 부터 끝까지 Connection 객체를 통한 쿼리실행이 모두 그 안에 나열돼 있다면 유지보수 하는 사람 뿐 아니라 개발하는 당사자조차도 가독하기 힘들다.

따라서 DB 래퍼 클래스를 만들어서 쿼리실행에 필요한 인자(사용자가 입력한 조회조건 또는 사용자가 입력한 정보)를 세션빈이 전달하고, 실행 결과를 서로 주고 받는 패턴을 사용한다.

이처럼 DB래퍼 클래스를 통해 레코드를 조작하게 될 때, 세션빈이 생성한 Connection 객체를 DB 래퍼 클래스의 메소드에 콜할 때 인자로 함께 넘겨야한다.

세션빈이 Connection을 넘기지 않고 DB래퍼 클래스에게 Conenction을 핸들링하는 것을 맡길 경우, 5개의 DB래퍼 클래스 메소드를 호출하면 Connection 생성을 5번 하고, close() 명령을 통한 반환도 5번 일어나게 되는 셈이다. 다분히 비효율적이다.

이에 비해 DB래퍼가 자신을 호출한 클래스로부터 항상 Connection을 받아서 쓴다면 필요없는 자원낭비를 막을 수 있다.

이와 같은 원칙은 전체 시스템 구조에서 통일성 있게 적용돼야 할 것이다. 통일성 있게 적용되지 않으면 이런 원칙을 세우는 의미가 없다. 어떤 서브 패키지는 DB래퍼 클래스를 생성할 때 생성자에서 Connection을 받는다던가,
또는 아예 DB래퍼 클래스로 하여금 Connection을 관장하도록 한다던가 하면 전체 설계구조에 통일성이 없고 이를 바탕으로 한 추가 개발에 있어서도 비효율이 따르게 된다. 일관성 없는 구조의 클래스들을 재활용하는 신규 모듈도 역시 흐름에 일관성이 없을 것이다.

이제까지 언급했던 원칙을 패턴으로 사용하는 샘플소스를 보도록 하자.

아래 메소드는 사용자가 입력한 전표 내용으로 전표 등록을 하는 메소드이다. 이 세션빈 이름은 SlipManager이다.

소스보기

전표를 등록하는 비즈니스 프로세스는 사용자가 입력한 전표정보를 받아서 마감여부를 체크한 후, 전표 번호 MAX 값을 알아내고 전표 TABLE에 INSERT 하는 작업으로 요약된다. 물론 실제 업무는 이보다 훨씬 더 복잡한 경우의 수를 가지고 있다. 법인카드사용정보가 있는 경우 법인카드 사용정보에도 등록을 시켜야 하고, 예산에서 지출되는 전표일 경우 예산 정보에도 그 내역을 기록해야 하는 등 다양한 업무 정의를 포함하고 있다.

소스를 보면 알겠지만 위에서 정한 원칙대로 Connectin 객체는 지역변수로 선언하여 사용하고 있으며, DB래퍼 클래스의 메소드에 Connection 객체를 넘기고 있다.

메소드 오버로딩(OVERLOADING)
이제 다른 시스템의 업무가 이 메소드를 사용해야 하는 업무 요구사항이 생길 경우에 대해 살펴보겠다. 인사시스템에 복리후생비 신청정보가 결재자의 승인을 받으면 자동으로 전표가 발행되어야 하는 요구사항이 나왔다 치자.

프로그램 설계자는 전표등록이라는 비즈니스 프로세스를 구현한 메소드가 SlipManager라는 세션빈에 이미 있다는 것을 알고 있으므로, 이 부분은 따로 전표 등록 메소드를 만들지 않고 기존 것을 재사용하기로 한다.

아래소스는 이처럼 위의 세션빈을 전표등록 메소드를 호출하는 복리후생비 신청정보 승인 프로세스를 보여주고 있다.

소스보기

위 소스를 보면 전표등록 메소드를 재사용 하고 있으나 한가지 우려되는 점이 있다. 바로 세션빈의 메소드를 콜 하는 것 까지는 문제가 없으나, 전표 등록 호출시 세션빈에게는 Connection 객체를 던지지 않으므로 호출되는 세션빈이 Connection을 따로 생성시켜 작업을 하고 있다.

위와 같은 업무에서는 세션빈의 메소드를 한번 호출하고 있지만 만약 사용자가 업무 복리후생비전표를 100장 생성시켜야 하는 경우라면 아래와 같은 for loop 문으로 콜 하게 될 것이다.

소스보기

결국 Connection은 100개가 생성되고, 100개가 반환되는 작업이 일어난다. 역시나 시스템 자원운용 면에서 보면 상당한 문제점을 야기시키는 부분일 것이다.

따라서 이와같이 타 세션빈에서 호출하는 메소드일 경우, DB 래퍼와 통신시에 Connection 객체를 넘겼듯 세션빈 메소드와 통신시에 Connection을 넘겨서 사용하는 패턴으로 가야 할 것이다.

위의 전표생성메소드를 Connection을 받아서 작업하는 메소드로 고쳐서 설계해보자. 물론 이 Connection을 생성시키고 닫아주는 주체는 전표생성 메소드를 콜한 세션빈이 하게 된다. 그렇다면 아래 소스처럼 regSlip 메소드를 오버로딩 한 메소드를 추가로 개발한다.

소스보기

이러한 메소드가 추가되면 먼저 만들어 두었던 regSlip 메소드는 아래와 같이 regSlip(Connection conn , SlipInfo[] sInfos)를 호출하자. 실제 비즈니스 프로세스는 regSlip(Connection conn , SlipInfo[] sInfos)안에 모두 있으므로, 이 메소드를 기존 그대로 놔두면 중복코드가 있게 된다.

소스보기

이와 같이 세션빈 메소드는 다른 세션빈 메소드가 업무상 호출할 수 있는 경우가 빈번하므로, 세션빈 메소드를 Connection 받는 것과 안 받는 것으로 오버로딩하는 패턴을 사용하게 된다.

세션빈 메소드는 다수 쿼리를 실행시키는 집합체
EJB 기반 프로젝트의 가장 큰 영역은 세션빈이다. 세션빈 내에서 모든 비즈니스 프로세스가 구현되므로, 세션빈 안에서 기타 클래스들을 어떠한 패턴으로 사용할 지 고민하며 설계하고 구현해야 재사용성이 높고 강력한 모듈을 만들 수 있다.

다음 강의에서는 실제 업무에서 일어나는 복잡한 프로세스를 구현함에 있어서 DBMS의 펑션이나 프로시져를 사용하는 것의 장점을 살펴보도록 하겠다.

2회까지는 세션빈 사용에 있어서의 클래스 패턴과, 시스템 성능 측면에서 어떤 점을 피해가야 하는지가 주된 논점이었다. 실제 프로젝트에서는 이 정도로 대부분의 이슈가 커버되겠지만, 막상 구현 결과물을 보면 세션빈 메소드가 수많은 쿼리를 실행시키는 집합체로 보인다.

그렇다면 이것을 단 한 번의 실행으로 끝내버리는 방법은 없을까. 다음 강의에서 그 방법에 대해 논의해보자. @