Whiteship's Note


초고속 로보트 손

모하니?/Watching : 2009.09.15 22:56




오오... 굉장하군요... 굉장해. 마지막에 핸드폰 잡는게 예술입니다.

top

TAG 로보트

[Expert One-on-One J2EE Design and Development] 실용적인 데이터 접근 2

Spring/J2EE D&D : 2009.09.15 21:05


참고: Expert One-on-One J2EE Design and Development 9장

일반적인 JDBC 추상 프레임워크

JDBC API와 그 이슈를 이해하는 것으로 충분하지 않다.

JDBC API를 사용할 때는 항상 도무이 클래스를 사용하여 애플리케이션 코드를 간편화하라. 하지만, O/R 맵핑 계층을 손수 작성하지는 말자. O/R 맵핑이 필요하다면 기존의 솔루션을 사용하라.

동기

저수준 JDBC 코딩을 하는 것은 고통스럽다. 문제는 SQL이 아니라, 코드량이다. JDBC API는 일부 엘레강트하지 않으며 애플리케이션 코드에서 사용하기에는 너무 저수준이다.

SQL 자체는 간단하지만 이 쿼리를 조회하는데 30줄의 JDBC 코드가 필요하다. 거기에 중첩 try-catch까지 보니까 프레임워크 클래스로 리팩토링하고 싶은 강한 욕구를 느낀다.

*고수준 추상화 API*

간단한 추상화 프레임워크를 만들어서 JDBC API 사용을 훨씬 간편하게 할 수 있다.

AvailabilityQuery  availabilityQuery  =  new AvailabilityQuery (ds); 
List 1= availabilityQuery.execute(1, 1) ;

이렇게 할 수 있다. 이 쿼리 객체를 재사용할 수 있으며, execute() 메서드는 런타임 예외를 던진다. 즉 복구가 가능할 경우 필요에 따라 예외를 잡아서 처리할 수도 있다. JDO의 Query 인터페이스와 비슷하다.

목표

파레토 원칙을 기억하는가(80:20) 최선의 결과는 JDBC 추상화 계층으로부터 얻을 수 있다. 추상화 계층은 다음 요소들에 초점을 맞춘다.

- 너무 많은 코드
- 에러 발생시 깔끔한 클린업 - 대부분이 깨진 try-catch를 사용하는데 이를 방지한다.
- SQLException 다루리 - 검증형 예외일 필요가 없다.
- 사용자 코드를 작성하기 쉽게한다.

놀랍게도 이 문제를 다루는 프레임워크와 라이브러리가 많지 않다. 따라서 내가(로드 존슨) 예제 애플리케이션에서 사용할 용도로 프레임워클 개발했다.

이게 유일한 방법은 아니지만, 간단하고 매우 효율적이다.

예외 처리

JDBC API는 예외를 어떻게 사용하지 말하야하는지에 대한 교훈이다.

JDBC는 단일 예외 클래스를 사용한다. "뭔가 잘못됐다" 라는 것 빼고는 알 수가 없다. 이미 살펴봤다시피, 벤더 관련 코드와 SQL 예외를 구분할 수 있다.

다음은 모든 RDBMS에서 의미가 있는 에러들이다.
- 문법 에러
- 데이터 무결성 제약 위반
- SQL의 값을 부적절한 타입의 변수에 바인딩 시도

이런 문제는 java.lang.SQLException의 하위 클래스로 만들어져야 한다. 우리가 만들 추상화 프레임워크에서 풍부한 에러 계층 구조를 제공할 것이다.

JDBC 에러 처리를 할 때 다음의 이슈들도 다룬다.

- 우리가 만들 추상화 계층 코드가 JDBC에 묶이지 않기를 원한다. DAO 패턴 구현체에서 사용할 수 있도록 의도하고 있다. 만약 DAO를 사용하는 코드가 SQLException 같은 특정 리소스에 국한된 것을 처리해야 한다면 비즈니스 로직과 (DAO 패턴을 구현한)데이터 접근 구현체 간의 디커플링(decoupling)을 할 수가 없다. (메서드 시그너처에 들어가기 떄문에..) 따라서 JDBC API는 JDBC 관련 예외를 사용하더라도 우리가 만들 예외 계층 구조는 JDBC에 묶이지 않게 만들겠다.

- 4자에서의 검증형 예외와 비검증형 예외 논의에 따라 모든 예외를 런타임 예외로 만들겠다. JDO가 이런 접근 방법을 통해 좋은 결고를 냈다. JDBC 예외는 대부분 복구가 불가능하기 때문에 이런 방법이 적절하다.

- EJB를 쓴다면 블라 블라. 생략.

일반적인 데이터 접근 예외 계층구조

일반적인 데이터 접근 예외 계층구조를 만들어서 위와 같은 요구사항을 만족시킬 수 있다. JDBC 사용에만 국한 되지 않고 데이터베이스를 사용하는 DAO에서 모두 사용할 수 있다.

계층 구조의 최상위는 DataAccessException 클래스다. NestedRunitmeException을 상속받았다. 이 상위 클래스는 감쌓아야 할 예외를 스택 트레이스에 유지할 수 있게 해준다. 자세한건 4장에..

DataAccessException은 구체적인 데이터 접근 문제를 나타내는 하위 클래스를 가지고 있다.
- DataAccessResourceFailureException
자원을 가져오지 못한 경우에 발생. JDBC용 구현에서는 데이터베이스에서 connectino을 가져오지 못했을 때 발생한다.
- CleanupFailureDataAccessException
작업 수행을 잘 마치고 깨끗히 정리를 못했을 때 발생. JDBC용 구현에서는 Connection을 닫지 못했을 때 발생.
- Datalntegr ityViolationExcept ion
- InvalidDataAccessApiUsageException
- InvalidDataAccessResourceUsageException
부적절한 SQL을 날릴 때 발생
- OptimisticLockingViolationException
- DeadlockLoserDataAccessException
- UncategorizedDataAccessException
차마 분류하지 못한 것들...

JDBC 예외를 일반적인 예외로 변환하기

지금까지는 SQL 예외를 일반적인 예외로 변환하는 방법을 생각하지 않았다. 그렇게 하려면 SQLState 코드와 벤더 코드를 분석해야 한다. 하지만 SQLState로는 모든 문제를 분석하기에 적절치 않다. RDBMS 구현체 특화된 옵션들을 분석해야 한다.

변환 기능을 제공하는 인터페이스를 만든다. (보통 인터페이스를 사용하는 설계가 이식성을 얻을 수 있는 최선책이다.) 이 책에서는 SQLExceptionTranslater 인터페이스를 구현할 것이다.

public interface SQLExceptionTranslater {
    DataAccessException translate(String task, String sql, SQLException  sqlex);
}

*SQLState 코드를 사용하는 SQLExceptionTranslater 기본 구현체 코드*

정적인 데이터 구조(HasSet)를 만들어서 if/else 문을 줄였다.(묶을 수 있는 에러덩어리들을 하나의 맵에 넣어두고 map.contains()를 사용하여 if문 사용을 줄였군요.. 오호...)

*오라클에 특화된 구현체 OracleSQLExceptionTranslater*

벤더 코드가 SQLState 코드 보다 훨씬 많다.

2 단계 추상화 계층

강력하면서도 데이터베이스에 종속적이지 않은 예외 처리 계층 구조를 가지게 되었다. JDBC 사용을 간편하게하는 추상화 프레임워크를 구현해보자.

JDBC 프레임워크를 2 단계 추상화 계층으로 나눌 수 있다.

낮은 수준의 추상화는 com.interface21.jdbc.core 패키지에 있다. JDBC 워크프롤우와 예외 처리를 담당한다. 콜백 접근 방법을 취하였으며 애플리케이션 코드가 콜백 인터페이스를 구현토록 한다.

높은 수준의 추상화는 com.interface21.jdbc.core.object 패키지에 있다. JDO와 비슷하게 보다 객체 지향적인 방법을 제공한다. RDBMS 작업을 자바 객체로 표현할 수 있다.

JDBC 워크프롤우와 에러 처리를 담당하는 프레임워크

낮은 수준의 추상화는 JDBC 쿼리를 보내고 SQLException을 SQLExceptionTranslater를 사용하여 일반적인 예외 계층 구조로 변환한다.

다시 보는 "Inversion of Control"

일반적인 클래스 라이브러리 있는것처럼 애플리케이션 코드가 인프라 코드를 호출하는 것이 아니라.. 인프라 코드가 애플리케이션 코드를 사용하도록(이 부분이 "IoC"라 불리는 접근방법) 복잡한 예외 처리 코드를 그(인프라 코드) 안으로 넣어서 해결하는 방법을 보았다. 이런 접근 방법을 사용한 패키지를 "프레임워크"라 하고 그렇지 않은 것을 라이브러리라 한다.

com.interface21.jdbc.core 패키지

이 패키지에서 가장 중요한 클래스는 JdbcTemplate이다(오오.. 드디어.. 등장이다.) 핵심 워크 프롤우와 애플리케이션 코드 호출을 담당한다. jdbcTemplate에 있는 코드는 PreparedStatements 생성을 위임하여 쿼리를 실행하고 그 결과를 JDBC ResultSet에서 뽑아내는 콜백을 사용한다. 그 두 인터페이스가 바로 PreparedStatementCreator와 RowCallbackHandler 인터페이스다. 애플리케이션 개발자는 이 두 인터페이스 구현체만 만들면 된다. JDBC statement를 직접 실행하거나 예외를 처리할 필요가 없다.

preparedStatementCreator 인터페이스와 관련 클래스

preparedStatementCreator 인터페이스는 java.sql.PreparedStatement를 만들 애플리케이션 클래스가 반드시 구현해야 한다. 즉 SQL과 매개변수만 바인딩 시키면 jdbcTemplate이 실행해줄 것이다. 이것을 구현할 때 Connection을 가져오는 것이나 SQLException 처리는 신경쓰지 않아도 된다.

public interface PreparedStatementCreator {
PreparedStatement createPreparedStatement(Connection conn)
throws   SQLException; }

PreparedStatementCreatorFactory는 동일한 SQL에 매번 다른 매개변수로 PreparedStatementCreator 객체를 만들 때 도와주는 클래스다. 이 클래스는 높은 수준 추상화 프레임워크에서 빈번하게 사용한다.

RowCallbackHandler 인터페이스와 관련 클래스

RowCallbackHandler는 쿼리가 반환하는 ResultSet의 행에서 컬럼 값을 축출하는 클래스가 구현해야하는 인터페이스다. JdbcTemplate이 모든 ResultSet을 순회할 때 사용한다. 이 인터페이스는 SQLException을 그대로 나뒀다. JdbcTemplate이 처리한다.

public  interface RowCallbackHandler  {
void processRow(ResultSet  rs)   throws  SQLException; }

구현체는 컬럼의 수와 데이터 타입을 알고 있어야 한다.

RowCountCallbackHandler 클래스는 이 인터페이스의 구현체로 컬럼 이름과 타입 그리고 ResultSet의 행 갯수에 대한 정보를 가지고 있다. 비록 구체적인 클래스이지만, 애플리케이션 클래스가 이것을 상속받아서 사용한다.

ResultReader 인터페이스는 RowCallbackHandler를 확장하여 가져온 결과를 java.util.List에 저장하는 인터페이스다.

그밖의 클래스

JdbcTemplate 클래스는 SQLExceptionTranslator 객체 하나를 사용하여 SQLException을 일반화된 예외 계층구조로 변환한다. 중요한 건 JdbcTemplate의 동작을 매개변수화 했다는 것이다.

DataSourceUtils 클래는 javax.sql.DataSrouce 에서 Connection을 가져오는 static 메서드, SQLException을 일반화된 계층 구조로 변환하는 메서드, Connection을 닫는 메서드, JNDI에서 DataSource를 가져오는 메서드를 담고 있다.

JdbcTemplate은 DataSourceUtils 클래스를 사용한다.

JdbcTemplate 클래스의 핵심 워크 플로우

이 API가 전반적으로 java.sql.Connection 객체가 아니라 DataSource를 사용하는 이유는 다음과 같다.
- 그렇게 하지 않으면 connection을 어디에선가 얻어와야 하는데 그럼 애플리케이션 코드가 복잡해지고 DatsSrource.getConnection() 메서드를 사용한다면, 애플리케이션 코드에서 SQLException을 처리해야 할 것이다.

- JdbcTemplate이 사용한 connection을 닫는 것이 중요한데 connection을 닫을 때 예외가 발생할 수 있기 때문에 모든 JDBC 예외를 처리를 우리 프레임워크에 맡기고 싶다. 그러나 connection을 사요하면 connection을 외부에서 가져오고 그것을 JdbcTemplate에서 닫는 건 이상하고 이미 닫혀버린 connectino을 사용할 수 있는 여지를 만들게 된다.

JdbcTemplate 클래스 사용하기

조회하기

*JdbcTemplate 사용하여 조회(query)하는 예제 코드*

익명 내부 클래스르 사용하여 RowCallbackHandler와 PreparedStatementCreator 인터페이스를 구현했다.

가장 중요한 것은 JdbcTemplate을 사용함으로써 주요 에러 발생을 제거했다는 것이다. Connection이 닫히지 않을 위험이 없어졌다.

갱신하기

*JdbcTemplate 사용하여 갱신(update)하는 예제 코드*

PreparedStatementCreator 구현체를 만들고 그 객체를 jdbcTemplate.update()에 넘겨주면 끝. 단 한줄이다.


top


[Expert One-on-One J2EE Design and Development] 실용적인 데이터 접근 1

Spring/J2EE D&D : 2009.09.15 20:28


참고: Expert One-on-One J2EE Design and Development 9장

데이터 접근 기술 선택

J2EE 애플리케이션에서 사용할 수 있는 데이터 접근 기술 들을 두 개의 카테고리로 분류할 수 있다. SQL 기반과 O/R 맵핑 기반이다.

SQL 기반 기술

JDBC

JDBC는 SQL을 기반으로 한다. 저장 프로시저, 커스텀 쿼리, RDBMS에 특화된 기능들을 사용할 때 적절하다.

중 요한 것은 우리가 JDBC를 어떻게 사용하느냐이다. 나이브한(안일한) 접근 방법은 애플리케이션 코드에서 JDBC 코드와 SQL문을 사용하는 것인데 이것은 큰 재앙을 가져올 것이다. 전체 애플리케이션을 특정 영속화 전략에 묶어버려서, 데이터 스키마가 바뀔 때 문제가 생길 것이다. 하지만 다음의 가이드라인만 지킨다면 애플리케이션 코드에서 JDBC를 효율적으로 사용할 수 있다.

- JDBC 접근 코드를 비즈니스 로직에서 최대한 분리하라. JDBC 코드는 오직 DAO에만 있어야 한다.
- JDBC API를 직접 사용하는 저수준(쌩짜) JDBC 코드를 기피하라. JDBC 에러 처리는 생산성을 심각하게 저하할 정도로 난잡하다. 에러 처리같은 저수준의 구체적인 내용들은 도우미 클래스로 빼버리고 고수준 API를 사용하라. SQL 제어권에 지장없이 그렇게 하는 것이 가능하다.

JDBC를 쓰는 것에 대해서는 아무 문제가 없다 다면, 세션 EJB같은 비즈니스 객체나 DAO에서 조차 JDBC를 직접 사용하지는 말자. 비즈니스 컴포넌트를 저수준 JDBC에서 분리할 수 있는 추상화 계층을 사용하라.

SQLJ

생략

O/R 맵핑 기술

O/R 맵핑 기술은 JDBC나 SQLJ 같은 API와는 전혀 다른 프로그래밍 모델이다. 이것을 사용해서도 J2EE 애플리케이션에서 DAO를 구현할 수 있다.

기업 수준에서 사용할만한 O/R 맵핑 솔루션을 제공하는 오픈 소스 제품이 없다.(로드 존슨이 이 책을 쓸 당시 하이버네이트가 없었거나.. 그리 알려지지 않았었나 봅니다.), 

상용 제품

생략

TopLink

생략

CoCoBase

생략

JDO

JDO는 J2EE에 중요한 기술이지만, 미션-크리티컬한 엔터프라이즈 애플리케이션에서 사용하기에는 아직 성숙도가 증명되지 않았다.(역시 책이 쓰여진 시점을 신경쓰며 읽으셔야겠죠)

샘플 애플리케이션에서 사용할 데이터 접근 기술 선택하기

(어찌저찌해서 JDBC 선택! 캐싱할 껀덕지도 별로 없고 저장 프로시저도 효율적으로 사용해야 하기 때문이라고 하는군요.)

JDBC 자세히 보기

그간의 경험을 통해 심각한 문제를 야기할 수 있는 J2EE 애플리케이션에서 sloppy(지져분한, 더러운, 진흙물이 튄것 같은) JDBC 코드를 보았다.

*에러 처리를 제대로 못한 예제 코드*
(대부분의 JDBC 코드와 con.close(); 까지 하나의 try-catch 문으로 묶여있습니다.)

이렇게 코딩을 하면 전체 애플리케이션이 깨지거나 데이터베이스가 뻗을 수 있다. con.close()까지 가기 전에 SQLException이 발생하면, 커넥션을 반환하지 못할 것이기 때문이다.

*에러 처리를 제대로 한 예제 코드*
(con.close();를 finally 블럭에서 null 체크를 한 다음에 수행하면서 try-catch로 묶어줬습니다.)

이렇게 하는것이 좀 더 견고하지만 너무 장황하다. 가장 간단한 JDBC 기능을 하나 수행하는데 39줄의 코드가 필요하다. 자.. 이제 JDBC API를 직접 쓰는것 말고 더 좋은 어떤 방법이 필요하다는 것을 분명히 알 수 있다.

SQLException에서 정보 축출하기

JDBC 예외 처리에서 또 다른 중요한 것은 SQLExcpetion에서 최대한 정보를 뽑아내는 것이다. 불행히도 JDBC는 이 걸 매우 복잡하게 만든다.

java.sql.SQLException은 getNextException() 메서드로 다음 SQLException을 받아올 수 있다. JDBC는 모든 예외 상황에 대해 오직 한 개의 예외 클래스만 사용하고 있다. 또한 벤더 코드와 SQLState 코드를 포함하고 있다.

벤더코드는 getErrorCode() 메서드가 반환하는 int 값이다. 그 이름이 암시하듯이 벤더 코드는 벤더마다 다를 수 있다. 따라서 이 코드에 의존하는 것은 이식성을 저해한다.

getSQLState() 메서드는 5자리 문자열을 반환한다. 이론적으로는 이식이 가능한 에러 코드다. 처음 두 자리는 고수준의 정보를 담고 있고 다음 세자리는 보다 상세한 정보를 담고 있다.

불행히도 SQLState 코드는 모든 데이터베이스에서 지원하지 않으며 문제에 대해 충분한 정보를 제공해주지도 못한다. 때에따라 벤더 코드가 구체적인 정보를 얻을 수 있는 유일한 방법이 된다. 대부분의 데이터베이스는 SQLState 코드보다 훨씬 많은 벤더 코드를 제공하며 더 잘 정리된 문서를 제공하고 있다.

java.sql.SQLWarning 예외도 SQLException 만큼이나 잘 이해할 필요가 있다. SQLException처럼 검증형 예외(checked exception)지만, JDBC API가 실제로 던지지는 않는다. 치명적이지 않은 SQL 에러로 여기고 그 내용을 ResultSet, Statement, Connection에 덭 붙여준다. 이런 JDBC API에 getWarnings() 메서드를 호출해서 가져올 수 있다.

이런 Warning이 JDBC를 직접사용하면 좋치않은 이유 중 하나다. JDBC를 직접사용하는 코드에서 Warging을 확인하는 과정을 추가하면 111줄의 코드가 더 추가된다. 실제로 하기엔 너무 많다.

PreparedStatement 질문

java.sql.PreparedStatement 인터페이스와 java.sql.Statement 인터페이스의 차이를 이해하는 것이 중요하다. prepared statent는 변수들을 바인딩할 위치와 각각의 위치에 매개변수를 설정할 수 있는 기능을 제공한다.

SQL에 적합한 문자 형태로 바꿔주는 역할을 해준다.

문자열 보다 더 효율적이다. 처음 prepared statement를 실행하면 파싱되고 데이터베이스에 의해 컴파일 되며 statement 캐시에 저장될 수 있다. 반면, 일반적인 SQL 문자열은 매번 변수 값이 달라질 경우 이렇게 캐시를 할 수가 없다.

JDBC 2.0은 prepared statement 캐싱을 필수로 하지 않았지만, 3.0에서는 표준으로 정했다.
top


봄싹 9월 특강 Completed!! 후기랄까나...

모하니?/Thinking : 2009.09.15 01:48



켄트벡 세미나와 KSUG 번개에 나갔었다면, 볼 수 있었겠지만 개인 사정으로 그동안 토비님을 못 뵈었다가 드디어 봄싹 스터디에 초대하여 3시간짜리 스터디 진행을 부탁드렸습니다. 커피 한 잔과 감자탕 한 끼로 너무 많이 부려먹은듯(?)해서 죄송스럽지만, 뭐.. 제자에게 이정도쯤은.. 해주셔야.. ㅎㅎㅎ 그저 감사 할 따름입니다. (__)/

이번에 사부님을 만나 느낀점은 많지만 그 중에서 개발자로서의 고민이 좀 더 심화되었습니다. 사실 다음 DebDay를 다녀왔을 때부터 느끼던 것인데 털어놓진 않고 속으로 앓고 있었지요.

문제의 핵심은 제가 작성한 코드가 개떡같다는 거였습니다.

그 개떡같은 코드는 현재 제 노트북에만 있고 그 어디에도 공개하지 않았습니다. 저번 달인가 이번 달 초에 다음 DevDay 때문에 제주도에  갔었을 때 작성한 코드가 정말 최악이었습니다. 스프링 코드를 거의 사용하지 않고 Smack과 java.net 패키지를 주로 이용하여 코드를 작성했었는데 정말 끔찍했습니다. 조금씩 계속 지져분해지기 시작하더니 어느 순간 걷잡을 수 없는 형태의 코드가 되어버렸고... 그 뒤는... 동작하긴 하지만 속은 다 썪어서 도무지 남에게 보여줄 수 없는 코드가 되었습니다.

그러던 중... 봄싹 스터디에서 스프링의 가장 기초이자 핵심은 DI가 어떤 것인지 보여주는 명쾌한 코드와 설명을 보면서 다시 한 번 자극을 받을 수 있었습니다. 스프링의 핵심인 DI가 어떤 과정으로 탄생되는 것인지 살펴보았는데, 그 과정이 굉장히 논리적이고 깔끔했습니다.

거기에다, 비밀리에 베타리딩 중인 책에서도 스프링이 어떤 문제를 어떤 방법으로 해결해나가는지 순차적으로 보여주는 내용이 저에게 많은 도움이 되고 있습니다.

스프링의 가장 초기 모습을 볼 수 있는 빨간책 1권과 코드를 중심으로 살펴보면서 스프링 DI 감각을 익히는 것이 중요하고 재밌겠다고 생각했습니다. 그래서 갑작스래 봄싹 스터디에 '오리지널 스프링' 스터디를 만들었는데, 생각보다 많은 분들이 참여하고 계십니다. 슬슬 스터디 홍보로 전환되는 듯 한데 어서 마무리하고 좀 더 공부하다 자야겠습니다.

밤늦게 글을 써서 그런지 두서가 없는데, 결론은 토비님 덕분에 스프링을 좀 더 진지하게 공부하게 되었다는 것입니다. 부디.. 개떡같은 코드가 찰떡같은 코드로 거듭나길 바라며~~ 열공!!
top