Whiteship's Note


스프링 DAO 3파전

모하니?/Coding : 2010.07.02 13:00


JDBC를 사용하는 부류가 있고, iBatis를 사용하는 부류가 있고, 하이버네이트를 사용하는 부류가 있다. 사실 이보다 더 다양한 영속화 기술들이 있지만.. 이 세 부류로 간추려 보려고 한다. 자 이들은 각각 어떻게 코딩을 하고 있을까.

public interface MemberDao {
void add(Member member);
void update(Member member);
Member get(int id);
List<Member> list();
void delete(int id);

}

이런 인터페이스를 각 부류에서 구현한다고 생각해보자.

아참, 전제는 스프링을 사용한다는 것이다.

1. JDBC

@Repository
public class MemberDaoJdbc implements MemberDao{
@Autowired SimpleJdbcTemplate jdbcTemplate;
public void setMemberMapper(RowMapper<Member> memberMapper) {
this.memberMapper = memberMapper;
}

RowMapper<Member> memberMapper = new RowMapper<Member>(){
public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
Member member = new Member();
member.setId(rs.getInt("id"));
member.setName(rs.getString("name"));
member.setJoined(rs.getDate("joined"));
return member;
}};
public void add(Member member) {
jdbcTemplate.update("insert into member(id, name, joined) values (?, ?, ?)", 
member.getId(), member.getName(), member.getJoined());
}

public void delete(int id) {
jdbcTemplate.update("delete from member where id = ?", id);
}

public Member get(int id) {
return jdbcTemplate.queryForObject("select * from member where id = ?", memberMapper, id);
}

public List<Member> list() {
return jdbcTemplate.query("select * from member", memberMapper);
}

public void update(Member member) {
jdbcTemplate.update(
"update member set name = :name, joined = :joined where id = :id", 
new BeanPropertySqlParameterSource(member));
}

}

대충 이런 코드가 된다. 스프링이 제공해주는 SimplJdbcTemplate은 멀티 쓰레드 환경에서 공유하면서 사용해도 안전한 객체이다. 따라서 빈으로 등록해놓고 주입받아서 쓴다고 한들 문제될 것이 없다. 

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/testContext.xml")
@Transactional
public class MemberDaoJdbcTest {
@Autowired MemberDaoJdbc memberDao;
@Test
public void di(){
assertThat(memberDao, is(notNullValue()));
}
@Test
public void crud(){
Member member = new Member();
member.setId(1);
member.setName("whiteship");
member.setJoined(new Date());
memberDao.add(member);
assertThat(memberDao.list().size(), is(1));
member.setName("기선");
memberDao.update(member);
assertThat(memberDao.get(1).getName(), is("기선"));
memberDao.delete(1);
assertThat(memberDao.list().size(), is(0));
}
}

테스트는 대충 만들었기 때문에 너그럽게 봐주시길...^_^;;;

2. iBatis

스프링에 iBatis의 SqlMapClient를 만들어 주는 팩토리빈을 등록해야 한다.
   
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="SqlMapConfig.xml" />
</bean>

아이바티스 설정 파일도 추가한다. 저 설정에 보이는 SqlMapConfig.xml이 필요하다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">

<sqlMapConfig>
<sqlMap resource="sample/ibatis/Member.xml" />
</sqlMapConfig>

그 다음 Member와 관련있는 SQL을 모아둔 Member.xml 파일을 만든다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-2.dtd">

<sqlMap namespace="Member">
<typeAlias alias="Member" type="sample.domain.Member" />

<delete id="delete" parameterClass="int">
delete from member where id = #id#
</delete>

<insert id="add" parameterClass="Member">
insert into member (id, name, joined) values(#id#, #name#, #joined#)
  </insert>
 
<update id="update" parameterClass="Member">
update member set name = #name#, joined = #joined# where id = #id#
</update>
 
<select id="get" parameterClass="int" resultClass="Member">
select * from member where id = #id#
  </select>

<select id="list" resultClass="Member">
select * from member order by id
  </select>
</sqlMap>

XML 엘리먼트와 어트리뷰트는 별도의 학습이 필요하지 않을만큼 직관적이다.. 이제 DAO 코드를 작성하자.

@Repository
public class MemberDaoIbatis implements MemberDao {
@Autowired SqlMapClientTemplate sqlMapClientTemplate;
public void add(Member member) {
sqlMapClientTemplate.insert("add", member);
}

public void delete(int id) {
sqlMapClientTemplate.delete("delete", id);
}

public Member get(int id) {
return (Member) sqlMapClientTemplate.queryForObject("get", id);
}

@SuppressWarnings("unchecked")
public List<Member> list() {
return sqlMapClientTemplate.queryForList("list");
}

public void update(Member member) {
sqlMapClientTemplate.update("update", member);
}

}

흠 많이 짧아졌다. 그런데.. 전체 코드량은 더 많이 늘은것 같지 않은가? 코드량이 척도의 전부는 아니지만.. 왠지 작업이 줄었다가 보다는 DAO에서 할일을 설정파일로 미뤘다는 느낌이 강하다. 아참 여기서 사용한 SqlMapClientTemplate도 SimleJdbcTemplate과 마찬가지로 빈으로 등록해서 사용해도 별 지장이 없는 쓰레드-안전한 클래스기 때문에 빈으로 등록해놓고 사용했다.

3. Hibernate

이것도 역시 빈을 하나 설정해줘야한다.
 
   <!-- ============================================================= -->
    <!--  Hibernate                                                    -->
    <!-- ============================================================= -->
    <bean id="transactionManager"
          class="org.springframework.orm.hibernate3.HibernateTransactionManager"
          p:sessionFactory-ref="sessionFactory"/>

    <bean id="sessionFactory"
          class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="sample.domain" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>

이번건 설정이 제법 길다. 그리고 빈이 하나가 아니라 두갠데.. 그건 나중에 TransactionManager에 대한 글을 올릴 때 설명해야겠다. 아니면 그냥 토비님 책을 보기 바란다. 11장에 자세히 설명해주시고 있다. TransactionManager와 기반 기술의 관계(?)랄까나.. 암튼.. 이제 바로 DAO를 만들 수 있다. 아 아니다 일단 Member 클래스에 애노테이션 부터 추가하자.

@Entity
public class Member {

@Id
int id;

...
}

이렇게 두 군데에 각각 하나씩만 붙여주면 된다. 이제 DAO를 만들자.

@Repository
public class MemberDaoHibernate implements MemberDao{
@Autowired SessionFactory sessionFactory;

public void add(Member member) {
getSession().save(member);
}

public void delete(int id) {
getSession().createQuery("delete from Member where id = ?")
.setInteger(0, id)
.executeUpdate();
}

public Member get(int id) {
return (Member) getCriteria()
.add(Restrictions.eq("id", id))
.uniqueResult();
}

@SuppressWarnings("unchecked")
public List<Member> list() {
return getCriteria().list();
}

public void update(Member member) {
getSession().update(member);
}

private Criteria getCriteria() {
return getSession().createCriteria(Member.class);
}

private Session getSession() {
return sessionFactory.getCurrentSession();
}
}

SQL이 없다. SQL처럼 보이는 HQL(굵은 글씨)만 보인다. 이렇게만 하면 정말 DAO가 구현되는지 의심스럽겠지만 정말 구현이 끝난다. 하지만 하이버네이트 Session과 Criteria API는 약간 학습이 필요하다. 자주 쓰는 API는 몇개 안되기 떄문에 대략 1시간 정도면 익힐 수 있다.

셋 개 다 해본결과.. 난 역시 하이버네이트가 편하다.
top


[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