0. intro
#


회사에서 Altibase 라는 국산 메모리기반 RDBMS를 사용하기로 결정함에 따라 하이버네이트 매핑을 위한 커스텀 다이얼렉트를 만들게 되었습니다.
필요한 기능만을 고려해 만들었기 때문에, Dialect 클래스 전체를 다 보지는 못했지만, 사실 매핑에 필요한 것 이상은 그다지 볼 필요는 안느껴지더군요.

Altibase용 dialect 소스는 아래와 같습니다.

package com.nextware.comm.persist.hibernate.dialect;

import java.sql.Types;

import net.sf.hibernate.Hibernate;
import net.sf.hibernate.dialect.Dialect;
import net.sf.hibernate.dialect.StandardSQLFunction;

/**
 @author FP team
 @version 2005. 4. 1.
 */
public class AltibaseDialect extends Dialect {

    public AltibaseDialect() {
        super();
        registerColumnType(Types.CHAR, "CHAR(1)");
        registerColumnType(Types.VARCHAR, 32767"VARCHAR($l)");
        registerColumnType(Types.BIGINT, "BIGINT");
        registerColumnType(Types.DECIMAL, "DECIMAL");
        registerColumnType(Types.DOUBLE, "DOUBLE");
        registerColumnType(Types.FLOAT, "FLOAT");
        registerColumnType(Types.INTEGER, "INTEGER");
        registerColumnType(Types.BIT, "NUMBER(1,0)");
        registerColumnType(Types.BIGINT, "NUMBER(19,0)");
        registerColumnType(Types.SMALLINT, "NUMBER(5,0)");
        registerColumnType(Types.TINYINT, "NUMBER(3,0)");
        registerColumnType(Types.INTEGER, "NUMBER(10,0)");
        registerColumnType(Types.NUMERIC, "NUMERIC(19, $l)");
        registerColumnType(Types.REAL, "REAL");
        registerColumnType(Types.SMALLINT, "SMALLINT");
        registerColumnType(Types.DATE, "DATE");
        registerColumnType(Types.TIME, "DATE");
        registerColumnType(Types.TIMESTAMP, "DATE");
        registerColumnType(Types.BLOB, "BLOB");

        registerFunction("lower"new StandardSQLFunction());
        registerFunction("upper"new StandardSQLFunction());
        registerFunction("ltrim"new StandardSQLFunction());
        registerFunction("rtrim"new StandardSQLFunction());

        registerFunction("to_char"new StandardSQLFunction(Hibernate.STRING));
        registerFunction("to_char",
                new StandardSQLFunction(Hibernate.TIMESTAMP));
        registerFunction("to_date",
                new StandardSQLFunction(Hibernate.TIMESTAMP));
    }

    public String getAddColumnString() {
        return "add";
    }

    public String getSequenceNextValString(String sequenceName) {
        return "select " + sequenceName + ".nextval from SEQTBL";
    }

    public String getCreateSequenceString(String sequenceName) {
        return "create sequence " + sequenceName;
    }

    public String getDropSequenceString(String sequenceName) {
        return "drop sequence " + sequenceName;
    }

    public boolean supportsSequences() {
        return true;
    }

    public boolean supportsLimit() {
        return true;
    }

    public String getLimitString(String sql, boolean hasOffset) {
        return new StringBuffer(sql.length() 20).append(sql).append(
                hasOffset ? " limit ?, ?" " limit ?").toString();
    }
}

1. 생성자
#

1-1. super() 호출
다른 다이얼렉트 소스들을 봐도 생성자에서 반드시 super() 를 호출해주고 있는데, 이것은 Dialect 추상클래스의 protected 디폴트 생성자에서 count, avg, max, min, sum 등의 하이버네이트 기본 지원 sql function을 등록해주는 작업을 해주기 때문입니다.
따라서, 위의 function을 사용하지 않을리는 없기 때문에, 반드시 생성자에서는 super() 를 호출해줘야 합니다.

1-2. 컬럼 매핑타입 설정
registerColumnType 메써드는 하이버네이트 타입과 컬럼의 데이터 타입을 매핑시켜주는 메써드입니다. 위의 소스의 예처럼, registerColumnType(Types.CHAR, "CHAR(1)"); 라고 해주면, 하이버네이트의 캐릭터 타입은 테이블의 char(1) 로 상호변환된다는 뜻이지요.
이 컬럼타입 등록과 관련해서는 기반 DBMS의 데이터 타입에 대한 설명을 자세히 읽어보시면서 해주시면 됩니다.
특히, 주의하실 점은, 위의 예에서 registerColumnType(Types.VARCHAR, 32767, "VARCHAR($l)"); 라고 되어 있는데, 하이버네이트 varchar 타입, 즉 String의 경우, 32767 byte까지는 Altibase varchar 타입으로 선언해주는 것입니다. Altibase에서는 varchar 타입에 대해 32767 byte까지 지원하기 때문이지요.
다른 예로, MySQL의 경우 String을 처리함에 있어서 255 byte까지는 varchar, 그 이상은 각각 text, mediumtext, longtext로 매핑하게 됩니다.

exMySQLDialect.java
registerColumnTypeTypes.VARCHAR, "LONGTEXT" );
registerColumnTypeTypes.VARCHAR, 16777215"MEDIUMTEXT" );
registerColumnTypeTypes.VARCHAR, 65535"TEXT" );
registerColumnTypeTypes.VARCHAR, 255"VARCHAR($l)" );

1-3. fuction 등록하기
super() 호출을 통해 등록한 count, avg, max, min, sum을 제외한 다른 function 들은 registerFunction 메써드를 사용하여 등록할 수 있습니다. registerFunction("lower", new StandardSQLFunction()); 와 같이 다른 인자값을 넘기지 않는 function의 경우 new StandardSQLFunction() 으로 등록시켜주면 되고, registerFunction("to_char", new StandardSQLFunction(Hibernate.STRING)); 와 같이 인자값이 필요한 경우엔 인자값의 하이버네이트 타입을 넘겨주면 됩니다.(왠만한 것들은 기존 Dialect 들에 구현되어 있으니 필요한 function 설정은 다른 Dialect들을 참조하시면 됩니다. ^^;)

2. 메써드 구현하기
#

Dialect 추상클래스 API를 보면 무려 50개가 넘는 메써드가 정의되어 있습니다. 개중에는 default 값을 반환하거나 세팅해주는 메써드도 있고, 또한 mapping 혹은 unsupported operation exception 등을 던지는 경우도 있습니다.
따라서, 사용자는 해당 DBMS spec에 맞게 해당 메써드를 overriding 해주면 됩니다.
몇가지만 살펴보자면, getAddColumnString 메써드는 schemaupdate를 사용할 때, 호출되는 컬럼 추가관련 DDL 문을 정의해주는 메써드입니다. 즉, alter table foo add column (name char(1)); 이냐 alter table foo add (name char(1)); 이냐의 차이에 따라 해당 메써드를 구현해주면 됩니다. 참고로 MySQLDialect는 add column 을 반환하도록 되어 있습니다.
만약 DBMS 고유의 키생성 방식이 시퀀스 방식이라면 위의 소스를 살펴보시면 쉬울 것입니다. getSequenceNextValString() 의 경우 시퀀스 값을 얻어오는 부분인데, 이를테면 Oracle9Dialect의 경우 "select " + sequenceName + ".nextval from dual" 문자열을 반환하도록 되어 있습니다. Altibase의 경우 dual 이라는 시스템테이블을 지원하지 않는 관계로 별도의 처리방법을 사용했지만, 그 부분은 Altibase를 직접 사용하시는 분들이 아니라면 필요하지 않기 때문에 설명하지 않겠습니다.(혹시 필요하신 분이 계시다면 개인적으로 문의하세요^^;)
시퀀스의 경우 HibernateSequence 라는 하나의 이름으로 생성됩니다. 때문에, 여러 곳에서 시퀀스를 사용하여 pk를 자동생성할 경우, 번호가 이어지지 않는 아쉬움이 있을 것도 같더군요.(오라클 테스트를 해보지 않은 관계로 오라클에서도 하나의 시퀀스만을 사용하는지는 확답드리지 못하겠습니다. 써보신 분들의 댓글 환영합니다^^)
supportsLimit 메써드는 거의 반드시 overriding 해야 할 메써드일텐데, 페이징 처리를 함에 있어 limit 함수를 지원하는지 여부를 반환합니다. 오라클의 경우엔 false 를 반환하겠죠. 만약 supportsLimit 에서 true를 반환한다면, getLimitString 에서 위와 같은 방식으로 limit 처리문장을 넣어주면 됩니다. Oracle9Dialect 에서는,

public String getLimitString(String sql, boolean hasOffset) {
  StringBuffer pagingSelect = new StringBuffersql.length()+100 );
  if (hasOffset) {
   pagingSelect.append("select * from ( select row_.*, rownum rownum_ from ( ");
  }
  else {
   pagingSelect.append("select * from ( ");
  }
  pagingSelect.append(sql);
  if (hasOffset) {
   pagingSelect.append(" ) row_ where rownum <= ?) where rownum_ > ?");
  }
  else {
   pagingSelect.append(" ) where rownum <= ?");
  }
  return pagingSelect.toString();
 }
이처럼 rownum 으로 페이징 처리하는 방식을 통째로 넣어놓았더군요. ^^;

3. outro
#

뭐, 사실 저야 필요에 의해 다이얼렉트를 만들게 되었습니다만, 왠만한 DBMS들은 하이버네이트에서 미리 지원을 해주니 별 걱정이야 없겠죠.
저도 단지 이 기회에 매핑 관련 소스를 좀 뜯어보게 된게 좋았다 싶어서 올려둔 겁니당. 심심하신 분들은 한 번 매핑쪽 소스 뜯어보세요ㅋㅋㅋ

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-1) was last changed on 06-Apr-2006 09:45 by UnknownAuthor