티스토리 뷰

영원한 고민거리, Java에서의 데이터베이스와 SQL 접근


Java 개발자에게 있어 데이터베이스와 SQL 접근은 영원한 고민거리이다. 요즘같이 생산성이 중요시되는 시대에 공부 목적이 아닌 이상 java.sql.* 패키지만을 사용하여 Plain JDBC로 데이터베이스 접근 코드를 구현하는 사람은 적을 것이다. Plain JDBC만을 사용하면 간단한 SQL 쿼리 결과를 얻기 위해 불필요한 Boilerplate Code(보일러플레이트 코드)가 너무 많아 프로젝트의 규모가 조금만 커져도 생산성의 발목을 잡게 된다.

MyBatis, JdbcTemplate, Hibernate...


이러한 생산성을 향상시켜주는 라이브러리가 존재한다. 먼저 너무나도 유명한 MyBatis는 iBatis의 전신으로 국내 프로젝트 환경에서 광범위하게 쓰이고 있다. 하지만 XML을 무지하게 싫어하는 나로서는 XML이 대부분을 차지하는 MyBatis를 사용하기 꺼려진다. 다음 후보인 JdbcTemplate은 엔터프라이즈 자바 진영에서 너무나도 절대적 위치를 차지하고 있는 Spring 덕에 역시 많이 사용되는 라이브러리이다. 현재 내가 주력으로 사용하고 있기는 하지만 POJO(Plain Old Java Object)와 쿼리의 맵핑에 있어 아쉬운 점이 많다. 그렇다면 Hibernate는 어떨까? 2001년 EJB가 외면받던 시대에 혜성같이 나타나 사람들의 환호를 받은 ORM(Object-Relational Mapping) 라이브러리이다. Plain SQL이 존재하지 않는 데이터베이스 접근으로 미래의 신기술과도 같아 보였다. 하지만 지금은? Java 관련 어느 포럼을 가도 대부분의 개발자들이 ORM과 지겨운 씨름을 하다가 Plain SQL이 옳았다며 ORM을 탈출하고 있는 추세이다.


최소한의 기능만 가진 작고 직관적인 라이브러리는 없을까?


오늘 소개하는 sql2o가 바로 이런 역할에 충실한 라이브러리이다. 특징은 아래와 같다.

  • 작고 빠르다. 제작자는 동일 쿼리에 대해 가장 빠른 시간이 소요되었다고 주장하고 있다.
  • 복잡성을 유발하는 별도의 XML, Annotation(어노테이션) 설정이 필요없다. 개발자는 소스 코드 작성에만 집중할 수 있다.
  • 자동 컬럼 맵핑을 지원한다. 컬럼명이 DATE_CREATED라면 오브젝트의 dateCreated 변수에 자동 맵핑해준다. 오라클 사용자라면 정말 편리한 기능이다.
  • 소스 코드의 가독성을 높여주는 Named 파라메터를 지원한다.
  • 트랜잭션 제어를 지원한다.
  • Batch(배치) 처리를 지원한다.
  • 날짜/시간 컬럼에 대한 Joda-Time 타입 맵핑을 지원한다.
  • 2014-03-31에 최신 버전이 나온 상태로 지속적으로 업데이트되고 있다.

공식 홈페이지에 설명된 예제를 살펴보면 정말 꼭 필요한 기능만 최소한의 소스 코드로 구현할 수 있도록 만들어져 있다. 미니멀리즘을 지향하는 나같은 개발자에게는 상당히 매력적인 라이브러리라고 할 수 있다. 보다 자세한 내용은 아래 링크를 참고하자. 당장 쓰고 싶은 마음이 들 정도로 설명과 문서화가 정말 잘 되어있다.

사용법 정리


오브젝트 맵핑


  • sql2o는 반드시 SELECT 문의 결과를 맵핑할 POJO 클래스가 존재해야 한다. POJO 클래스의 각 변수는 SELECT 문의 각 컬럼명과 대소문자 구분없이 일치해야 한다. 1개라도 일치하지 않을 경우 org.sql2o.Sql2oException: Property with name '{}' not found on class 오류가 발생한다.
  • List<Map<String, Object>> 오브젝트 맵핑은 지원하지 않는다. v1.5.0 버전부터 지원 예정이다.

파라메터 맵핑


Player player = new Player();
player.setPlayerTeamName("Chicago Bulls");
String sql = "SELECT PLAYERNO, PLAYERNAME WHERE PLAYERTEAMNAME = :playerTeamName";
List<Player> players = sql2o.createQuery(sql).bind(player).executeAndFetch(player.class);
  • bind() 메써드를 사용하면 자동으로 POJO 오브젝트를 파라메터로 자동 맵핑할 수 있다.
  • 주의할 점은 SQL 문에 삽입된 모든 파라메터명은 파라메터로 전달될 POJO 클래스의 변수명과 대소문자까지 정확히 일치해야 한다. 1개라도 일치하지 않을 경우 java.lang.IllegalArgumentException: Parameter not found 오류가 발생한다.
  • POJO 오브젝트의 입력된 모든 변수 또한 SQL 문에 파라메터로 존재해야 한다. 1개라도 존재하지 않을 경우 java.lang.IllegalArgumentException: Parameter not found 오류가 발생한다.

에외 처리


sql2o는 오류 발생시 org.sql2o.Sql2oException 또는 RuntimeException을 발생시킨다. 발생한 예외 클래스와 메시지를 정확히 알고 싶으면 아래와 같이 catch() 문을 작성한다. org.apache.commons.lang.exception.ExceptionUtils 클래스는 Apache Commons Lang 라이브러리가 제공하는 클래스이다.

catch (Sql2oException ex) {
  // 처리할 로직 작성
}
catch (RuntimeException ex) {
  if (ExceptionUtils.getRootCause(ex) instanceof IllegalArgumentException) {
      // 처리할 로직 작성
  }
}

INSERT


Player player = new Player();
Player.setPlayerNo("33");
Player.setPlayerName("Pippen");
String sql = "INSERT INTO PLAYER(PLAYERNO, PLAYERNAME) VALUES(:playerNo, :playerName);
sql2o.createQuery(sql).bind(Player).executeUpdate();

UPDATE


Player player = new Player();
Player.setPlayerNo("45");
Player.setPlayerName("Jordan");
String sql = "UPDATE PLAYER SET PLAYERNO = :playerNo WHERE PLAYERNAME = :playerName);
sql2o.createQuery(sql, false).bind(Player).executeUpdate();
  • UPDATE, DELETE 문 작성시 createQuery() 메써더의 returnGeneratedKeys 값을 false로 주지 않으면 org.sql2o.Sql2oException: Error in executeUpdate, 허용되지 않은 작업 오류가 발생한다.

CREATE


String sql = "CREATE TABLE TEAM (TEAMNO NUMBER(5), TEAMNAME VARCHAR2(50));
sql2o.createQuery(sql, false).excuteUpdate();
  • CREATE 문 작성시 파라메터 맵핑은 지원되지 않는다.

발견된 문제점


Oracle 쿼리 조회시 DATE 타입 컬럼의 경우 시간 데이터가 누락된다.


Oracle 11g에서 DATE 타입의 데이터를 조회시 날짜만 가져오고 시간이 누락되는 것을 발견했다. DATE 타입은 java.sql.Date, org.joda.time.DateTime 2개 타입으로 받을 수 있는데 java.sql.Date 타입 자체가 날짜 정보만 저장할 수 있기 때문에 발생한 문제이다.

String sql = "SELECT SYSDATE FROM DUAL";
Date date = sql2o.createQuery(sql).executeScalar(Date.class):
DateTime dateTime = sql2o.createQuery(sql).executeScalar(DateTime.class):
System.out.println(date);
System.out.println(dateTime);

2014-04-19
2014-04-19T00:00:00.000Z

TIMESTAMP 타입으로 조회하면 시간 데이터가 정상적으로 받아진다. java.sql.Timestamp, org.joda.time.DateTime 2개 타입으로 받을 수 있다.

String sql = "SELECT SYSTIMESTAMP FROM DUAL";
Timestamp timestamp = sql2o.createQuery(sql).executeScalar(Timestamp.class):
DateTime dateTime = sql2o.createQuery(sql).executeScalar(DateTime.class):
System.out.println(timestamp);
System.out.println(dateTime);

2014-04-19 12:05:19.687498
2014-04-19T12:05:19.709Z

DATE 타입을 TIMESTMAP 타입으로 캐스팅 후 조회해보자 시간 데이터가 정상적으로 받아진다.

String sql = "SELECT CAST(SYSDATE AS TIMESTAMP) AS \"SYSDATE\" FROM DUAL";
Timestamp timestamp = sql2o.createQuery(sql).executeScalar(Timestamp.class);
DateTime dateTime = sql2o.createQuery(sql).executeScalar(DateTime.class);
System.out.println(timestamp);
System.out.println(datetime);

2014-04-19 12:38:57.0
2014-04-19T12:38:57.000Z

결론은 아래와 같다.

  • Oracle 11g에서는 DATE 타입 조회시 시간 정보가 누락되는 버그가 있다. 쿼리문에서 TIMESTAMP로 형변환을 하면 정상적으로 조회된다. 버그가 수정되기 전까지는 Oracle 11g에서는 sql2o를 사용하지 않는 것을 추천한다.

관련 링크


참고 글


댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함