2011년 6월 8일 수요일

iBATIS in Action (13/14)

13. iBATIS 최적 활용 기법


13.1. iBATIS 에서 단위 테스트하기
단위 테스트는 현대 소프트웨어 개발 방법에서 매우 중요한 사항이 되었다. 비록 여러분이 익스트림 프로그래밍 혹은 그 외의 다른 애자일 개발 방법론의 장점에 대해 동의하지 않더라도 단위 테스트는 소프트웨어 개발 생명주기에서 주춧돌이 되는 기법으로 사용해야 한다.
개념적으로 퍼시스턴스 단(tier)은 세 개의 계층으로 분리된다. 그림에서 보듯이 iBATIS는 각 계층들에서 단위 테스트를 쉽게 할 수 있게 해준다.

그림 13 1. 퍼시스턴스에 직접적으로 관련된 전형적인 계층들을 보여준다(퍼시스턴스와 관련 없는 계층은 이 그림에 없다).
iBATIS는 각 계층에서 최소한 세 가지 방법으로 테스트를 용이하게 해준다.
 매핑과 SQL 구문 그리고 매핑된 도메인 객체를 포함한 매핑 계층 자체를 테스트한다.
 DAO 계층을 테스트하여 DAO에 있을 수 있는 퍼시스턴스 고유의 로직을 테스트하도록 한다.
 DAO 의 소비 계층 내부에서 테스트한다.

12.5.2 매핑 계층 단위 테스트
이 테스트는 일반적으로 대부분의 애플리케이션에 존재하는 단위 테스트의 최하위 수준이다. 이 과정에서 SQL 구문과 이 구문에 매핑된 도메인 객체를 테스트한다. 이것은 테스트를 하려면 데이터베이스의 인스턴스가 필요함을 의미한다.

- 테스트용 데이터베이스 인스턴스
테스트용 데이터베이스 인스턴스는 Oracle 이나 마이크로소프트 SQL Server와 같이, 개발자가 실제로 사용하는 데이터베이스에서 실행되는 인스턴스일 것이다. 개발자의 환경이 단위 테스트에 적합하다면 준비하고 실행하는 것은 간단히 끝날 것이다. 저장 프로시저 같은 비표준 데이터베이스 기능을 사용할 경우에는 실제 데이터베이스를 사용할 필요가 있다. 저장 프로시저나 다른 데이터베이스로 이식 가능하지 않은 데이터베이스 설계를 선택하면, 실제 데이터베이스 인스턴스를 사용하는 것을 제외한 다른 방식으로는 데이터베이스 단위 테스트를 수행하기가 어려워진다.
실제 데이터베이스 인스턴스를 사용할 때의 단점은 단위 테스트가 네트워크에 연결돼 있을 때만 실행할 수 있다는 점이다. 대신, 로컬에 데이터베이스 인스턴스를 설치해서 사용할수도 있지만 단위 테스트를 실행하기 전에 추가적인 로컬 환경 설정 작업이 필요하게 된다. 어떤 방식을 선택하든지 간에 실제 데이터베이스를 사용하는 것은 테스트 스위트(suite)나 각각의 단위 테스트 사이 사이에서 테스트 데이터 혹은 스키마까지도 재구축해야 하는 문제에 직면할 수밖에 없다. 이렇게 작업하면 비록 대용량의 기업용 데이터베이스 서버를 사용하더라도 굉장히 느리게 수행된다. 다른 문제점으로, 중앙 집중적인 데이터베이스의 경우 여러 개발자들이 동시에 단위 테스트를 실행하면 충돌이 발생할 수도 있다. 그러므로 개발자별로 스키마를 분리해서 각각의 테스트가 서로 영향을 끼치지 않아야 할 것이다. 보다시피 이 방식의 일반적인 문제점은 단위 테스트가 꽤 큰 규모의 인프라스트럭처의 일부분에 의존한다는 점에 있다. 이것은 경험 많은 테스트 주도 개발자들이 보기에는 이상적이지 않다.
자바 개발자들은 운 좋게도, 상대적으로 표준적인 데이터베이스 설계의 단위 테스트를 매우 쉽게 실행할 수 있는 훌륭한 메모리 기반 데이터베이스를 최소한 한 개는 가지고 있다. HSQLDB는 전적으로 자바로 작성된 메모리 기반 데이터베이스이다. 이 데이터베이스를 작동시키기 위해서는 디스크상에 어떠한 파일도 만들 필요가 없고 네트워크 접속도 불필요하다. 게다가 Oracle이나 마이크로소프트 SQL Server 같은 전형적이 데이터베이스에서 대부분의 데이터베이스 설계를 그대로 가져다가 생성해서 쓸 수 있다. 비록 설계가 복잡하여(저장 프로시져처럼) 전체 데이터베이스를 다시 생성할 수는 없더라도 거의 대부분을 재생성하는 것이 가능하다. HSQLDB를 사용하면 스키마와 테스트 데이터를 포함하는 데이터베이스 재구축을 매우 빠르게 수행할 수 있다. iBATIS 자체를 위한 단위 테스트는 HSQLDB를 사용하고 각각의 개별적인 테스트마다 스키마와 테스트 데이터를 다시 생성한다. 우리는 개인적으로 데이터베이스에 의존적인 1,000개 정도되는 테스트들의 테스트 스위트를 HSQLDB를 사용하여 30초 이내에 테스트하였다. HSQLDB에 대한 자세한 정보는 http://hsqldb.sourceforge.net 에서 볼 수 있다. 마이크로소프트 .NET 개발자들도 대안으로 메모리 기반 데이터베이스 제품을 만드는 것뿐만 아니라 HSQLDB를 포팅하려는 시도가 이뤄지고 있음을 안다면 행복해 할 것 같다.
- 데이터베이스 스크립트
이제 데이터베이스 인스턴스는 해결되었으니, 스키마와 테스트 데이터에 관해서는 뭘 해줘야 할까? 개발자들은 아마도 데이터베이스 스키마와 테스트데이터를 생성하는 스크립트들을 만들 것이다. 스크립트들을 버전 관리 시스템(CVS나 서브버전 등)에 등록해 두었다면 더 좋다. 스크립트들은 애플리케이션에서 여느 다른 코드들처럼 다루어진다. 비록 개발자가 데이터베이스에 대한 제어 권한이 없다 하더라도 권한이 있는 누군가가 정기적으로 수정을 하고 있어야 한다. 애플리케이션의 소스 코드와 데이터베이스 스크립트는 항상 동기화가 되어 있어야 하고, 거기에 있는 단위 테스트들도 확실히 동기화를 시켜야 한다. 단위 테스트 스위트를 실행할 때마다 스크립트를 실행하여 데이터베이스 스키마를 생성해야 한다. 이 방법을 사용하면 버전 관리 시스템에 새로운 데이터베이스 생성 스크립트들을 커밋하고서 변경된 사항들이 애플리케이션에 문제를 일으키지는 않는지 테스트해 볼 수 있다. 이게 이상적인 상황이다. 테스트를 실행하기 위해 HSQLDB 같은 메모리 기반 데이터베이스를 사용하면 스키마를 변환하는 추가 작업이 필요할 수도 있다. 수작업에 의한 오류를 피하고 통합 작업시간을 줄이려면 이 변환 작업을 자동화하도록 하라.
- iBATIS 설정파일 (SqlMapConfig.xml)
단위 테스트를 하려는 목적으로, 여러분은 iBATIS 설정 파일을 분리해서 사용하고자 할 것이다. 이 설정 파일은 데이터 소스와 트랜잭션 관리자를 다룬다. 이 설정은 테스트 환경과 운영 환경에서 매우 크게 차이가 나는 부분이다. 예를 들어 운영할 때는 J2EE 애플리케이션 서버 같은 관리 시스템 환경하에 놓이게 된다. 운영 환경에서는 아마도 JNDI에서 DataSource 인스턴스를 가져올 것이다. 또한 운영시에는 글로벌 트랜잭션을 사용할 수도 있다. 하지만 테스트 환경에서는 서버상에서 실행하지 않을 것이고 DataSource 설정도 간단하며 로컬 트랜잭션을 사용할 것이다. 테스트와 운영 환경의 설정 정보를 독립적으로 설정하는 가장 쉬운 방법은, 운영 설정 파일과 동일한 모든 SQL 매핑 파일을 참조하는 또 하나의 iBATIS 설정 파일을 사용하는 것이다.
- iBATIS SqlMapClient 단위 테스트
이제 데이터베이스 인스턴스, 자동화된 데이터베이스 빌드 스크립트 그리고 테스트용 설정 파일을 포함하여 미리 준비해야 할 것들을 모두 마련했으니 단위 테스트를 작성할 준비가 다 됐다. 다음은 JUnit을 사용하여 간단한 단위 테스트를 만드는 예를 보여준다.
public class PersonMapTest extends TestCase {
private SqlMapClient sqlMapClient;
public void setup () {
sqlMapClient = SqlMapClientBuilder.
build("maps/TestSqlMapConfig.xml");
runSqlScript("scripts/drop-person-schema.sql");
runSqlScript("scripts/create-person-schema.sql");
runSqlScript("scripts/create-person-test-data.sql");
}
public void testShouldGetPersonWithIdOfOne() {
Person person = (Person) sqlMapClient.
queryForObject("getPerson", new Integer(1));
assertNotNull("Expected to find a person.", person);
assertEquals("Expected person ID to be 1.",
new Integer(1), person.getId());
}
}

위의 예제는 자바용 단위 테스트 프레임워크인 JUnit 을 사용한다.(JUnit에 관해서는 www.junit.org 에서 더 많은 정보를 얻을 수 있다. .NET 프레임워크용으로 유사한 도구가 있는데 그 중 하나로 NUnit이 있고 www.nunit.org 에서 관련 정보를 찾을 수 있다). Setup 메서드에서 데이터베이스 테이블을 삭제한 뒤 다시 생성하고서 데이터를 재구축하였다. 테스트별로 모든 것을 재구축함으로써 다른 테스트의 영향을 받지 않도록 보장할 수 있다. 하지만 이 접근 방식은 Oracle이나 SQL Server 같은 RDBMS에서 사용하기엔 너무 느리다. 속도가 문제가 되는 경우에는 HSQLDB 같은 RDBMS에서 사용하기엔 너무 느리다. 속도가 문제가 되는 경우에는 HSQLDB 같은 메모리 기반 데이터베이스를 고려하라. 실제 테스트 케이스에서 레코드 하나를 가져와서 빈즈에 매핑하고 빈즈에 들어 있는 값이 우리가 기대했던 값과 같은지 확인한다.
이상이 매핑 계층을 테스트하는데 필요한 모든 것이다. 테스트할 필요가 있는 다음 계층은 DAO계층이다. 여기서는 애플리케이션이 DAO 계층을 포함하고 있다고 가정한다.

13.1.2 데이터 접근 객체(DAO) 단위 테스트하기
데이터 접근 객체 계층은 추상 계층이다. 추상 계층의 특성 때문에 DAO는 테스트하기 쉬울 것이다. DAO 는 또한 DAO 계층을 사용하는 부분의 테스트도 쉽게 만든다. 이번 절에서는 DAO 자체의 테스트에 대해 공부할 것이다. DAO는 일반적으로 인터페이스와 구현체로 분리돼 있다. DAO를 직접 테스트할 것이기 때문에 여기서 인터페이스의 역할은 없다. 테스트 목적으로 DAO 구현체를 사용해서 직접 작업할 것이다. 이렇게 하면 DAO 디자인 패턴의 사용법을 거스르는 것처럼 보이겠지만, 이게 바로 단위 테스트의 멋진점이다. 우리가 시스템 밖에서 나쁜 짓을 해도 그냥 내버려 둘 것이다!
DAO 레벨에서 테스트할 때는 가능하면 데이터베이스나 하부 인프라스트럭처를 사용하지 말아야 한다. DAO 계층은 퍼시스턴스 구현체를 위한 인터페이스이다. 하지만 DAO를 테스트할 때는 DAO 내부에 무엇이 있는지 테스트하는 것이지, DAO 외부에 있는 것을 테스트하려는 것이 아니다.
DAO 테스트의 복잡도는 오로지 DAO 구현체에 전적으로 달려 있다. 예를 들어 JDBC DAO를 테스트하는 것은 상당히 힘이 든다. Connection, ResultSet, PreparedStatement 등과 같은 전형적인 JDBC 컴포넘트들을 대체할 훌륭한 모의 객체 프레임워크가 필요한 것이다. 모의 객체 프레임워크가 있다고 하더라도 모의 객체의 복잡한 API를 관리하려면 많은 노력이 들어간다. iBATIS의 SqlMapClient 인터페이스의 모의 객체가 더 쉽다. 자, 한번 시도해보자.

- 모의 객체로 DAO 단위 테스트하기
모의 객체란 단위 테스트를 목적으로 실제 구현체를 대신해 사용하는 객체이다. 모의 객체는 일반적으로 많은 기능을 가지고 있지 않다. 모의 객체는 한 가지 경우만을 만족시킴으로써 단위 테스트가 추가적인 복잡성에 대한 걱정 없이 몇 가지 다른 영역에 집중할 수 있도록 해준다. 우리는 이번 예제에서 모의 객체를 사용하여 DAO 계층을 테스트하는 방법을 보여줄 것이다.
이 예제에서는 간단한 DAO를 사용한다. 트랜잭션 같은 것에 대해 신경쓰지 않도록 하기 위해서, 여기서는 iBATIS의 DAO 프레임워크를 사용하지 않을 것이다. 이 예제의 목적은 개발자가 어떤 종류의 DAO 프레임워크를 사용하는지에 관계 없이, DAO 계층을 테스트하는 것을 보여주려는 것이다.
첫째로 테스트하려는 DAO를 생각해보자. 다음은 13.1.1 절의 예제와 유사한 SQL Map을 호출하는 SqlMapPersonDao 구현체를 보여준다.
public class SqlMapPersonDao implements PersonDao {
private SqlMapClient sqlMapClient;
public SqlMapPersonDao(SqlMapClient sqlMapClient) {
this.sqlMapClient = sqlMapClient;
}
public Person getPerson (int id) {
try {
return (Person)
sqlMapClient.queryForObject("getPerson", id);
} catch (SQLException e) {
throw new DaoRuntimeException(
"Error getting person. Cause: " + e, e);
}
}
}
위에서 어떻게 SqlMapClient를 DAO의 생성자에 넣었는지 주의해서 보라. 이 방식은 DAO의 단위 테스트를 쉽게 할 수 있게 해준다. SqlMapClient 인터페이스의 모의 객체를 넘겨주면 되기 때문이다. 분명히 이 예제는 간단하고 별로 테스트할 것도 없어 보인다. 하지만 모든 테스트에는 의미가 있다. 다음에서 SqlMapClient의 모의 객체를 생성하고 getPerson() 메서드를 테스트하는 단위 테스트를 볼 수 있다.
public void testShouldGetPersonFromDaoWithIDofOne() {
final Integer PERSON_ID = new Integer(1);
Mock mock = new Mock(SqlMapClient.class);
mock.expects(once())
.method("queryForObject")
.with(eq("getPerson"),eq(PERSON_ID))
.will(returnValue(new Person (PERSON_ID)));
PersonDao daoSqlMap =
new SqlMapPersonDao((SqlMapClient) mock.proxy());
Person person = daoSqlMap.getPerson(PERSON_ID);
assertNotNull("Expected non-null person instance.",
person);
assertEquals("Expected ID to be " + PERSON_ID,
PERSON_ID, person.getId());
}

위의 예제는 JUnit 과 자바용 모의 객체 프레임워크인 jMock을 사용한다. 굵은 글씨체 부분에서 볼 수 있듯이 jMock으로 SqlMapClient 인터페이스의 모의 객체를 생성하면, 실제 SqlMapClient 나 함께 사용할 SQL XML 그리고 데이터베이스 등에 대한 걱정없이 DAO의 행위를 테스트 할 수 있게 된다. jMock은 편리한 도구로 www.jmock.org에서 더 많이 배울 수 있다. 이미 예상했겠지만 NMock이라고 불리는 .NET용 모의 객체 프레임워크도 있으며 http://nmock.org에서 찾아볼 수 있다.

13.1.3 DAO 소비자 계층 단위 테스트 하기
애플리케이션에서 DAO를 사용하는 또 다른 계층을 소비자(consumer)라고 부른다. DAO 디자인 패턴을 사용하면 퍼시스턴스 계층의 모든 기능을 구현할 필요 없이 이 소비자의 기능을 테스트할 수 있게 된다. 좋은 DAO 구현체는 사용 가능한 기능을 설명해줄 수 있는 인터페이스를 가지고 있다. 소비자 계층을 테스트할 수 있는 열쇠가 바로 이 인터페이스의 존재이다. 다음의 인터페이스를 살펴보자. 윗 절에 나왔던 getPerson() 메서드를 알아 볼 수 있을 것이다.
public interface PersonDao extends Dao {
Person getPerson(Integer id);
}

위의 인터페이스가 DAO 계층의 소비자 테스트를 시작하는 데 필요한 전부이다. 인터페이스의 완전한 구현체는 필요 없다. jMock을 사용하면 getPerson() 메서드의 행동을 쉽게 예측한 대로 흉내낼 수 있다. PersonDao 인터페이스를 사용하는 서비스를 살펴보자.
public class PersonService {
private PersonDao personDao;
public PersonService(PersonDao personDao) {
this.personDao = personDao;
}
public Person getValidatedPerson(Integer personId) {
Person person = personDao.getPerson(personId);
validateAgainstPublicSystems(person);
validateAgainstPrivateSystems(person);
validateAgainstInternalSystems(person);
return person;
}
}

단위 테스트의 대상은 DAO가 아니라 getValidatedPerson() 메서드가 수행하는 다양한 유효성 검사 같은 로직이다. 각 유효성 검사 메서드는 아마도 private 일 것이고, 논지를 벗어나지 않기 위해 여기서는 그냥 private 인터페이스만을 테스트한다고 치자.
좀 전에 살펴본 PersonDao 인터페이스 덕분에 데이터페이스 없어도 이 로직을 테스트하는 것은 쉬울 것이다. 우리가 해야 하는 것은 PersonDao의 모의 객체를 만들어서 서비스의 생성자에 넘겨 주고 getValidatedPerson() 메서드만 호출하는 것뿐이다. 다음에서는 앞에서 설명한 그대로 처리하는 단위 테스트를 볼 수 있다.
public void testShouldRetrieveAValidatedPerson (){
final Integer PERSON_ID = new Integer(1);
Mock mock = new Mock(PersonDao.class);
mock.expects(once())
.method("getPerson")
.with(eq(PERSON_ID))
.will(returnValue(new Person(PERSON_ID)));
PersonService service =
new PersonService((PersonDao)mock.proxy());
service.isPersonalInformationValid(
new Person(new Integer(1)), new Integer(1));
assertNotNull("Expected non-null person instance.",
person);
assertEquals("Expected ID to be " + PERSON_ID,
PERSON_ID, person.getId());
assertTrue("Expected valid person.",
person.isValid());
}

다시 JUnit과 jMock을 사용하였다. 위에서 보듯이 테스트 방식은 애플리케이션의 각 계층에서 일관적이다. 이렇게 하면 간단하고 관리하기 쉬운 단위 테스트에 집중하게 되어서 좋은 현상이라 할 수 있다.
여기까지 iBATIS에서의 단위 테스트에 대해 알아볼 만큼 알아보았다. 단위 테스트에 대한 일반적인 논의는 여러 곳에서 찾아볼 수 있다. 구글에서 “단위 테스트”로 검색해 봐라. 당신의 단위 테스트 스킬을 높여 줄 수 있는 많은 정보를 찾을 수 있으며, 아마도 여기서 설명한 것보다 더 나은 것도 발견할 수 있을 것이다.

13.2. iBATIS 설정 파일 관리하기
지금쯤이면 iBATIS가 설정과 SQL 구문 매핑을 위해 XML을 사용한다는 것이 명확하게 기억 속에 있을 것이다. 이 XML 파일들은 순식간에 커져서 다루기가 어렵게 될 수 있다. 이번 절에서는 SQL 매핑 파일들을 다루는 몇가지 좋은 방법들을 공부해 볼 것이다.
13.2.1 클래스패스 안에 두기
위치 투명성(Location Transparency)은 애플리케이션의 유지 보수성 측면에서 매우 중요하다. 이 위치 투명성을 통해 애플리케이션의 테스트와 배포를 간단하게 할 수 있다. /usr/local/myapp/config/ 나 C:\myapp\ 같은 고정적인 파일 위치에서 애플리케이션을 자유롭게 유지하게 하는 것도 위치 투명성의 한 가지이다. iBATIS에서 특정 파일 위치를 사용할 수 있다고 하더라도, 클래스패스를 사용하는 것이 더 낫다. 자바 클래스패스는 애플리케이션을 어떤 특정한 파일 경로에 얽매이지 않게 해준다. 클래스패스를 애플리케이션에서 클래스로더를 사용하여 참조할 수 있는 작은 파일 시스템이라고 생각해도 된다.
클래스로더는 클래스패스에서 클래스와 다른 파일들 같은 리소스를 읽어들일 수 있다. 예제를 살펴보자. 클래스패스에 아래와 같은 파일 구조가 있다고 상상해보자.
/org
/example
/myapp
/domain
/persistence
/presentation
/service

이 구조에서 완전한 클래스패스인 org/example/myapp/persistence를 사용하여 persistence 패키지를 참조할 수 있다. 매핑 파일을 둘만한 좋은 장소로 org/example/myapp/persistence/sqlmaps가 있겠다. 이것은 파일 구조에서 다음과 같이 보일 것이다.
/org
/example
/myapp
/domain
/persistence
/sqlmaps
SqlMapConfig.xml
Person.xml
Department.xml
/presentation
/service

위 구조 대신에 설정 파일들을 좀 더 단순한 구조에 두길 원한다면 설정파일들만 별도로 둘 수 있다. 예를 들어 config/sqlmaps를 사용할 수 있겠다. 그러면 아래와 같은 형태가 될 것이다.
/config
/sqlmaps
SqlMapConfig.xml
Person.xml
Department.xml
/org
/example
/myapp
/domain
/persistence
/presentation
/service

매핑 파일들을 클래스패스에 두면 iBATIS가 내장된 Resources 유틸리티 클래스를 이용해서 이 파일들을 쉽게 읽어들인다. Resources 클래스는 SqlMapBuilder와 호환되는 getResourceAsReader() 같은 메서드를 포함하고 있다. 위와 같은 형태의 클래스패스를 사용할 경우에는 다음과 같이 SqlMapConfig.xml 파일을 적재할 수 있다.
Reader reader = Resources
.getResourceAsReader("config/maps/SqlMapConfig.xml");

만약 데이터베이스 리소스 설정을 고정된 파일 경로처럼 한 곳에서 관리하는 위치에 둬야 할 필요가 있는 환경이라 하더라도, 여전히 매핑 파일은 클래스패스에 두어야 할 것이다.
이 방법은 SqlMapConfig.xml은 고정된 경로에 두지만 매핑 파일들은 여전히 클래스패스에 두는 복합적인 접근 방식을 사용하라는 의미이다. 예를 들어 아래 구조를 생각해보자.
C:\common\config\
/sqlmaps
SqlMapConfig.xml
/config
/sqlmaps
Person.xml
Department.xml
/org
/example
/myapp
/domain
/persistence
/presentation
/service

SqlMapConfig.xml 파일이 고정된 경로에 있다고 하더라도 내부적으로는 클래스패스의 XML 매핑 파일을 참조할 수 있다. 이로 인해 대부분의 리소스를 원하는 곳에 두고 부적절한 매핑 파일이 애플리케이션과 함께 배포될 가능성을 낮춰준다.


13.2.2 파일들을 함께 두자
매핑 파일들을 함께 둬라. 파일들을 클래스패스가 아닌 다른 곳에 이리저리 두는 행위를 삼가해라. 사용하는 클래스가 위치한 곳이나 다른 패키지에 별도로 매핑 파일을 두지 말라. 그렇게 하면 설정이 복잡해지고 어떤 매핑 파일들을 사용해야 할지 알기 어렵게 된다. 매핑 파일이 내부적으로 구조화되어 있기 때문에 추가적으로 분류하는 작업은 필요하지 않다. 파일 이름을 현명하게 짓고 XML 파일들을 한 디렉터리에 두도록 하라. 클래스를 매핑 파일과 같은 디렉터리(예를 들면 같은 패키지)에 두지말고 다른 XML 파일들과 섞이게 하지말라.
이렇게 하는 것은 일반적으로 매핑 파일과 프로젝트를 찾아보기 수월하게 해준다. 파일을 어디에 두더라도 iBATIS 프레임워크에게는 아무 상관이 없다. 하지만 동료 개발자에게는 많은 영향을 끼칠 것이다.

13.2.3 리턴타입 별로 정리하라
매핑 파일을 어떻게 정리하는가에 대한 가장 일반적인 질문으로, 무엇을 기준으로 정리하는가 하는 문제가 있다. 데이터베이스 테이블을 기준으로 정리할까? 클래스를 기준으로 하는 것은 어떨까? 어쩌면 SQL 구문 타입별로 정리하는 것은?
답은 “개발환경에 따라 다르다” 이다. 비록 ‘정확한’ 답이 없긴 하지만 그렇다고 맘대로 하지는 말라. iBATIS는 매우 유연해서 나중에 언제든지 SQL 구문을 이동시킬 수 있다.
처음에는 매핑 구문의 리턴타입과 매핑 구문의 파라미터를 기준으로 정리해 보는 게 가장 좋다. 이 방법으로 대개 개발자가 찾고자 하는 것을 기준으로 잘 찾을수 있게 매핑을 정리할 수 있다. 예를들어 Person.xml 매핑 파일에서는 Person 객체(혹은 Person 객체의 컬렉션)를 반환하는 매핑 구문이나 Person 객체를 파라미터로 받는 매핑 구문(insertPerson 혹은 updatePerson 처럼)이 있을 거라 예상할 수 있다.

13.3. 명명 규칙
iBATIS에는 이름을 지어줘야 하는 것이 매우 많을 수 있다. 매핑 구문, 결과 맵, 파라미터 맵, SQL Maps와 XML 파일들이 모두 이름을 필요로 한다. 따라서 이름을 짓는 데 특별한 규칙을 정해 놓는 것이 좋다. 여기서 한 가지 규칙을 논의할 것이지만 개발자 스스로가 규칙을 만드는 것에 주저하지 말라. 애플리케이션 내부에서 일관성 있게 사용하기만 한다면 문제될 것은 없다.

13.3.1 매핑 구문의 이름 짓기
일반적으로 매핑 구문은 사용하는 프로그래밍 언어의 메서드 명명 규칙을 그대로 따른다. 따라서 자바 애플리케이션에서라면 loadPerson 혹은 getPerson 과 같은 매핑 구문명을 사용한다. C#에서라면 SavePerson 혹은 UpdatePerson과 같은 매핑 구문명을 사용한다. 이러한 규칙을 따르면 일관성 있게 유지보수할 수 있을 뿐만 아니라 메서드 바인딩 기능이나 코드 생성 도구들을 사용할 수 있게 해준다.

13.3.2 파라미터 맵의 이름 짓기
대부분의 경우 파라미터 맵은 이름을 필요로 하지 않는다. 왜냐하면 명시적으로 파라미터 맵을 선언하는 것보다는 인라인 파라미터 맵을 사용하는 것이 훨씬 더 일반적이기 때문이다. SQL 구문의 특성상 파라미터 맵은 재사용할 가능성이 별로 없다. 일반적으로는 INSERT 구문과 UPDATE 구문에 동일한 파라미터 맵을 사용할 수 있다. 이러한 이유로 명시적으로 선언한 파라미터 맵을 사용한다면 Param 이라는 접미사를 파라미터 맵 이름 뒤에 붙여주기를 권한다. 예를 들어보자.


13.3.3 결과 맵 이름 짓기
결과 맵은 단일 클래스 타입과 결합되고 매우 빈번하게 재사용된다. 이러한 이유로 결과 맵은 결합되는 클래스 타입에 따라서 이름을 지으라고 권하고 있다. 또한 끝에 Result를 붙여준다. 예를 들어보자.


13.3.4 XML 파일들
iBATIS에는 두 가지 종류의 XML 파일이 있다. 하나는 주 설정 파일이고 다른 하나는 SQL 매핑 파일이 있다.

- 주 설정 파일
주 설정 파일은 원하는 어떤 이름이라도 붙일 수 있다. 그렇지만 우리는 SqlMapConfig.xml 이라고 짓기를 권장한다. 애플리케이션의 서로 다른 부분들에 대해 여러 개의 설정 파일을 만들고 싶다면 애플리케이션 모듈 이름을 접두사로 주어서 설정 파일 이름을 지으면 된다. 따라서 애플리케이션이 서로 다른 설정 파일을 사용하는 웹 클라이언트와 GUI 클라이언트로 구성돼 있다면 WebSqlMapConfig.xml 과 GUISqlMapConfig.xml 을 이름으로 사용할 수 있겠다. 또한 운영 환경이나 테스트 환경 처럼 어디에 배포를 하느냐에 따라서 다중 환경 설정을 가질 수도 있다. 이럴 경우에는 어떤 환경인지를 접두사로 지정하여 파일명을 정한다. 앞의 예제를 이어서 보자면 ProductionWebSqlMapConfig.xml과 TestWebSqlMapConfig.xml로 이름을 지을 수 있겠다. 이 이름들은 그 자체로 설명적이고 이러한 일관성 덕분에 여러 상이한 환경에서도 자동 빌드 기능을 구축하는 것이 가능해진다.

- SQL 매핑 파일
SQL 매핑 파일의 이름을 어떻게 짓는가 하는 것은 매핑 구문을 어떻게 구성하느냐에 달려있다. 이 책의 초반에 매핑 구문을 리턴 타입과 파라미터에 따라서 서로 다른 XML 파일로 구성하기를 추천했었다. 그렇게 했다면 파일 이름을 지을 때 리턴 타입과 파라미터 이름을 따라서 지으면 효과적이다. 예를 들면 한 개의 XML 매핑 파일이 Person 클래스와 관련된 SQL 구문을 포함하고 있다면 매핑 파일 이름을 Person.xml 이라고 짓는 것이 적절할 것이다. 이 방법은 대부분의 애플리케이션에서 효과적인 명명법일 것이다. 하지만 고려사항 몇 가지가 더 있다.
반환하는 결과는 동일하지만 서로 다른 데이터베이스용으로 사용하는 여러가지 형태의 SQL 구문이 필요할 수도 있다. 대부분의 경우 SQL은 이식 가능한 방법으로 작성할 수 있다. 예를 들어 iBATIS를 이용해 작성된 초기의 JPetStore 애플리케이션은 11개의 서로 다른 데이터베이스에서 작동할 수 있다. 하지만 때로는 이식할 수는 없지만 문제 해결에 가장 적합한 데이터베이스 전용 기능을 구현해야 할 경우도 있다. 이러한 경우에는 매핑 파일의 이름에 해당 매핑 파일이 사용하는 데이터베이스의 이름도 포함시켜 지어도 되고 때로는 데이터베이스의 이름을 포함시켜 짓는 것이 매우 중요한 경우도 있다. 예를 들어 Person을 위한 Oracle 전용 파일을 가지고 있다면 이 파일의 이름을 OraclePerson.xml 이라고 지을 것이다. 또 다른 방법으로 각 데이터베이스별로 데이터베이스의 이름을 딴 별도의 디렉터리를 만드는 방법이 있다. 하지만 이런 방법에 너무 집착하지는 말라. 파일이나 디렉터리 이름을 명확하게 짓고 그 이름에 적합하게 Oracle에 특화된 기능을 가진 매핑 파일이 되도록 하면 된다. Oracle에 의존적인 SQL 구문이 단 한 개 뿐이라면 해당 구문이 Oracle 이라는 단어를 포함하도록 이름을 짓는 것이 나을 것이다.

13.4. 빈즈, Map 혹은 XML?
iBATIS는 다양한 형태의 파라미터나 결과 매핑을 지원하다. 자바빈즈, Map(HashMap같은), XML 그리고 물론 원시 타입까지도 지원한다. 그럼 매핑 구문을 어디에 매핑해야 좋을까? 우리는 기본적으로 자바빈즈를 권장한다.

13.4.1 자바빈즈
자바빈즈는 최고의 성능, 최대의 유연성 그리고 타입 안정성을 제공해준다. 자바빈즈는 프로퍼티 매핑을 할 때 간단한 저수준 메서드 호출을 사용하기 때문에 빠르다. 자바빈즈에 프로퍼티를 추가한다고 해서 성능이 저하되지 않으며 다른 것들보다 메모리도 효율적으로 사용한다. 자바빈즈를 선택하게 만드는 더욱 중요한 점은 타입 안정성 때문이다. 이 타입 안정성 덕분에 iBATIS가 데이터베이스에서 반환받는 값에 적절한 타입을 부여하고 정확하게 바인딩할 수 있게 된다. Map이나 XML을 사용하여 겪게 되는 추측에 의한 바인딩이 없어진다. 또한 자바빈즈를 사용하면 더 큰 유연성을 확보하게 되는데 이는 자바빈즈의 getter와 setter 메서드를 직접 작성하여 데이터를 세밀하게 조정할 수 있기 때문이다.

13.4.2 Map
iBATIS에서는 두 가지 목적으로 Map을 지원하다. 첫째로 iBATIS는 Map을 사용하여 매핑 구문에 다양하고 복잡한 파라미터를 전송한다. 둘째로 가끔씩 데이터베이스에 있는 테이블이 단순히 Map-키와 값의 쌍-을 나타내기 때문이다.
하지만 Map은 형편없는 도메인 모델이다. 그러므로 비즈니스 객체를 표현하기 위해 Map을 사용해서는 안된다. 이것은 단지 iBATIS에 국한된 문제가 아니다. 어떤 퍼시스턴스 계층을 사용하든 간에 Map을 사용해서 도메인 모델을 나타내서는 안 된다. Map은 느리고 타입 안정성을 보장하지 않으며 자바빈즈보다 더 많은 메모리를 사용한다. 그리고 예측할 수 없는 행동을 하고 유지보수하기도 어렵다. 현명하게 판단해서 Map을 사용하라.

13.4.3 XML
iBATIS는 데이터베이스로 보낼 때나 혹은 그로부터 값을 반환받을 때 문서 객체 모델(DOM)혹은 단순한 문자열 형태로 XML을 직접 사용할 수 있다. XML을 사용할 때는 다소 값에 제한이 있지만 데이터를 이식 가능하고 파싱 가능한 형태로 빨리 바꾸고자 하는 대체로 간단한 애플리케이션에서는 유용하게 사용할 수 있다.
하지만 Map과 마찬가지로 도메인 모델을 나타내는데 XML을 우선 순위로 고려해서는 안된다. XML은 모든 형태들 중에서 가장 느리고 타입 안정성도 가장 낮으며 메모리도 가장 많이 사용한다. XML은 데이터를 표현하는 가장 최종 상태(예를 들면 보통은 HTML)에 가장 가깝다. 하지만 그런 이점은 다루기 힘들고 시간이 지남에 따라 유지보수하기 어렵다는 대가를 치루게 한다. Map과 마찬가지로 현명하게 판단해서 XML을 사용하라.

13.4.4 원시 타입 (primitives)
원시 타입은 iBATIS에서 직접적으로 파라미터나 결과 타입으로 사용할 수 있다. 두 경우 모두 원시타입을 사용하는 데 특별히 문제될 것은 없다. 원시타입은 빠르고 타입 안정성을 갖추고 있다. 당연히 데이터가 얼마나 복잡하냐에 따라 다소 제약을 받는다. 하지만 주어진 쿼리에 결과 행 수가 얼마인지를 세는 간단한 요구사항이 있을 때 정수 원시 타입은 좋은 선택이 될 수 있다. 요구사항만 만족시킨다면 원시 타입을 사용하는 데 주저할 것은 없다.

댓글 없음:

댓글 쓰기