S2JDBC-GenでOracleのDate型で時間を利用できるように改造

Oracleは9.2以降TIMESTAMP型(ナニ秒まで)ができて、それまでのDATE型(秒まで)と差別化がされました。標準のJDBCではDATE型はJavaの日付型か時間型にマッピングされるので、日付か時間のどちらかしか扱えなくなる可能性があります。

そこで、S2JDBC-Genを改造して、日時型で扱うように改造しました。

仕組み

S2JDBC-Genは各種データベースの方言対策として、ダイアレクトというファイルで制御しています。今回はこのファイルを編集しました。

変更点

EntityからDDLへの出力対策

sqlTypeMap.put(Types.SMALLINT, new SmallIntType("number(5,0)"));
sqlTypeMap.put(Types.TIME, new TimeType("date"));
sqlTypeMap.put(Types.VARCHAR, new VarcharType("varchar2($l)"));
// add
sqlTypeMap.put(Types.TIMESTAMP, new TimestampType("date"));

sqlTypeMapが並んでいる場所の最後で上記を追加しました。EntityがTypes.TIMESTAMPの場合dateでDDLを作成するようにします。この辺の構造はたぶん公開されていないと思いますが、いろいろ触りながら実験してみました。

スキーマからEntity作成時の対策

columnTypeMap.put("nvarchar2", OracleColumnType.NVARCHAR2);
columnTypeMap.put("raw", OracleColumnType.RAW);
columnTypeMap.put("varchar2", OracleColumnType.VARCHAR2);
// add
columnTypeMap.put("date", OracleColumnType.DATE);

2箇所変更が必要です。OracleColumnType.DATEの場合dateにしています。詳細はOracleColumnType.DATEの定義部分になります。

private static OracleColumnType TIMESTAMP = new OracleColumnType(
    "timestamp($s)", Date.class, TemporalType.TIMESTAMP);

// add
private static OracleColumnType DATE = new OracleColumnType(
    "date", Date.class, TemporalType.TIMESTAMP);

上記がOracleColumnType.DATEの定義部分になります。dateの場合TemporalType.TIMESTAMPに設定します。このTypeを見てS2JDBC-Genが処理をしているのだと思います。

注意

S2JDBC-Genスキーマからの取り込み。データの編集。DDL作成。マイグレードのチェックはしましたが、S2JDBCでは操作していません。実運用でS2JDBCを利用する場合には、素直にDATE型ではなくてTIMESTAMP型で作業した方が安全です。

また、この方法だとDATE型でもTIMESTAMP型でもDDL作成時にはDATE型にされてしまいます。今回はWindows標準のODBCではOracleのTIMESTAMP型がアクセスできないのと、S2Daoで作っているデータベースのスキーマ管理に使おうかなとS2JDBC-Genを検証しているので改造してみました。

感想

この辺はさすがにドキュメントが用意されていませんし、コメントも少ない場所だと思います。ただ。。。普通は触らない場所ですし、触らないべきです(爆)

今後

@GeneratedValueでAUTO_INCREMENT系のシーケンス処理がS2JDBCはできますが、これってデータベース側の処理は使わないでS2JDBCで生成した数値などを代入してくれているんですよね。

今回はスキーマ管理だけなので、それだと@GeneratedValueの意味がないので一手間かけてシーケンスのトリガーを作成するTaskを実装しようと思っています。030-sequenceフォルダのSQLを検索して、そのシーケンスに対応するトリガーSQLを作りたいと思います。

まあ、全体的になんでS2JDBC-Genを使っているのかって感じですが、この手のツールは最終生成物ではないので冒険してもいいかなって趣味で触っています。ある程度いけそうな予感はありますが、もう少し触ってだめそうだったらベタに管理したいと思います。逆に実案件だとS2JDBCはまだ若くて使えないかな。。。

全文

org/seasar/extension/jdbc/gen/internal/dialect/OracleGenDialect.java

/*
 * Copyright 2004-2008 the Seasar Foundation and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.seasar.extension.jdbc.gen.internal.dialect;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Types;
import java.util.Arrays;
import java.util.Date;

import javax.persistence.GenerationType;
import javax.persistence.TemporalType;

import org.seasar.extension.jdbc.gen.internal.sqltype.BigIntType;
import org.seasar.extension.jdbc.gen.internal.sqltype.BinaryType;
import org.seasar.extension.jdbc.gen.internal.sqltype.BlobType;
import org.seasar.extension.jdbc.gen.internal.sqltype.BooleanType;
import org.seasar.extension.jdbc.gen.internal.sqltype.ClobType;
import org.seasar.extension.jdbc.gen.internal.sqltype.DecimalType;
import org.seasar.extension.jdbc.gen.internal.sqltype.DoubleType;
import org.seasar.extension.jdbc.gen.internal.sqltype.IntegerType;
import org.seasar.extension.jdbc.gen.internal.sqltype.SmallIntType;
import org.seasar.extension.jdbc.gen.internal.sqltype.TimeType;
import org.seasar.extension.jdbc.gen.internal.sqltype.TimestampType;
import org.seasar.extension.jdbc.gen.internal.sqltype.VarcharType;

/**
 * Oracleの方言を扱うクラスです。
 *
 * @author taedium
 */
public class OracleGenDialect extends StandardGenDialect {

    /** テーブルが見つからないことを示すエラーコード */
    protected static int TABLE_NOT_FOUND_ERROR_CODE = 942;

    /** カラムが見つからないことを示すエラーコード */
    protected static int COLUMN_NOT_FOUND_ERROR_CODE = 904;

    /** シーケンスが見つからないことを示すエラーコード */
    protected static int SEQUENCE_NOT_FOUND_ERROR_CODE = 2289;

    /**
     * インスタンスを構築します。
     */
    public OracleGenDialect() {
        sqlTypeMap.put(Types.BINARY, new BinaryType("raw($l)"));
        sqlTypeMap.put(Types.BIGINT, new BigIntType("number($p,0)"));
        sqlTypeMap.put(Types.BLOB, new BlobType("blob"));
        sqlTypeMap.put(Types.BOOLEAN, new BooleanType("number(1,0)"));
        sqlTypeMap.put(Types.CLOB, new ClobType("clob"));
        sqlTypeMap.put(Types.DECIMAL, new DecimalType("number($p,$s)"));
        sqlTypeMap.put(Types.DOUBLE, new DoubleType("double precision"));
        sqlTypeMap.put(Types.INTEGER, new IntegerType("number(10,0)"));
        sqlTypeMap.put(Types.SMALLINT, new SmallIntType("number(5,0)"));
        sqlTypeMap.put(Types.TIME, new TimeType("date"));
        sqlTypeMap.put(Types.VARCHAR, new VarcharType("varchar2($l)"));
        // add
        sqlTypeMap.put(Types.TIMESTAMP, new TimestampType("date"));

        columnTypeMap.put("binary_double", OracleColumnType.BINARY_DOUBLE);
        columnTypeMap.put("binary_float", OracleColumnType.BINARY_FLOAT);
        columnTypeMap.put("blob", OracleColumnType.BLOB);
        columnTypeMap.put("clob", OracleColumnType.CLOB);
        columnTypeMap.put("long", OracleColumnType.LONG);
        columnTypeMap.put("long raw", OracleColumnType.LONG_RAW);
        columnTypeMap.put("nchar", OracleColumnType.NCHAR);
        columnTypeMap.put("nclob", OracleColumnType.NCLOB);
        columnTypeMap.put("number", OracleColumnType.NUMBER);
        columnTypeMap.put("nvarchar2", OracleColumnType.NVARCHAR2);
        columnTypeMap.put("raw", OracleColumnType.RAW);
        columnTypeMap.put("varchar2", OracleColumnType.VARCHAR2);
        // add
        columnTypeMap.put("date", OracleColumnType.DATE);
    }

    @Override
    public GenerationType getDefaultGenerationType() {
        return GenerationType.SEQUENCE;
    }

    @Override
    public boolean supportsSequence() {
        return true;
    }

    @Override
    public String getSequenceDefinitionFragment(String dataType,
            long initialValue, int allocationSize) {
        return "increment by " + allocationSize + " start with " + initialValue;
    }

    @Override
    public String getSqlBlockDelimiter() {
        return "/";
    }

    @Override
    public boolean isTableNotFound(Throwable throwable) {
        Integer errorCode = getErrorCode(throwable);
        return errorCode != null
                && errorCode.intValue() == TABLE_NOT_FOUND_ERROR_CODE;
    }

    @Override
    public boolean isColumnNotFound(Throwable throwable) {
        Integer errorCode = getErrorCode(throwable);
        return errorCode != null
                && errorCode.intValue() == COLUMN_NOT_FOUND_ERROR_CODE;
    }

    @Override
    public boolean isSequenceNotFound(Throwable throwable) {
        Integer errorCode = getErrorCode(throwable);
        return errorCode != null
                && errorCode.intValue() == SEQUENCE_NOT_FOUND_ERROR_CODE;
    }

    @Override
    public ColumnType getColumnType(String typeName) {
        if (org.seasar.framework.util.StringUtil.startsWithIgnoreCase(typeName,
                "timestamp")) {
            return OracleColumnType.TIMESTAMP;
        }
        return super.getColumnType(typeName);
    }

    @Override
    public SqlBlockContext createSqlBlockContext() {
        return new OracleSqlBlockContext();
    }

    @Override
    public String getSequenceNextValString(String sequenceName,
            int allocationSize) {
        return "select " + sequenceName + ".nextval from dual";
    }

    /**
     * Oracle用の{@link ColumnType}の実装クラスです。
     *
     * @author taedium
     */
    public static class OracleColumnType extends StandardColumnType {

        private static OracleColumnType BINARY_DOUBLE = new OracleColumnType(
                "binary_double", Double.class);

        private static OracleColumnType BINARY_FLOAT = new OracleColumnType(
                "binary_float", Float.class);

        private static OracleColumnType BLOB = new OracleColumnType("blob",
                byte[].class, true);

        private static OracleColumnType CLOB = new OracleColumnType("clob",
                String.class, true);

        private static OracleColumnType LONG_RAW = new OracleColumnType(
                "long raw", byte[].class);

        private static OracleColumnType LONG = new OracleColumnType("long",
                String.class);

        private static OracleColumnType NCHAR = new OracleColumnType(
                "nchar($l)", String.class) {

            @Override
            public String getColumnDefinition(int length, int precision,
                    int scale, String defaultValue) {
                return super.getColumnDefinition(length / 2, precision, scale,
                        defaultValue);
            }
        };

        private static OracleColumnType NCLOB = new OracleColumnType("nclob",
                String.class, true);

        private static OracleColumnType NUMBER = new OracleColumnType(
                "number($p,$s)", BigDecimal.class) {

            @Override
            public Class<?> getAttributeClass(int length, int precision,
                    int scale) {
                if (scale != 0) {
                    return BigDecimal.class;
                }
                if (precision < 5) {
                    return Short.class;
                }
                if (precision < 10) {
                    return Integer.class;
                }
                if (precision < 19) {
                    return Long.class;
                }
                return BigInteger.class;
            }
        };

        private static OracleColumnType NVARCHAR2 = new OracleColumnType(
                "nvarchar2($l)", String.class) {

            @Override
            public String getColumnDefinition(int length, int precision,
                    int scale, String defaultValue) {
                return super.getColumnDefinition(length / 2, precision, scale,
                        defaultValue);
            }
        };

        private static OracleColumnType RAW = new OracleColumnType("raw($l)",
                byte[].class);

        private static OracleColumnType TIMESTAMP = new OracleColumnType(
                "timestamp($s)", Date.class, TemporalType.TIMESTAMP);

        // add
        private static OracleColumnType DATE = new OracleColumnType(
                "date", Date.class, TemporalType.TIMESTAMP);

        private static OracleColumnType VARCHAR2 = new OracleColumnType(
                "varchar2($l)", String.class);

        /**
         * インスタンスを構築します。
         *
         * @param dataType
         *            データ型
         * @param attributeClass
         *            属性のクラス
         */
        public OracleColumnType(String dataType, Class<?> attributeClass) {
            super(dataType, attributeClass);
        }

        /**
         * インスタンスを構築します。
         *
         * @param dataType
         *            データ型
         * @param attributeClass
         *            属性のクラス
         * @param lob
         *            LOBの場合{@code true}
         */
        public OracleColumnType(String dataType, Class<?> attributeClass,
                boolean lob) {
            super(dataType, attributeClass, lob, null);
        }

        /**
         * インスタンスを構築します。
         *
         * @param dataType
         *            データ型
         * @param attributeClass
         *            属性のクラス
         * @param temporalType
         *            時制型
         */
        public OracleColumnType(String dataType, Class<?> attributeClass,
                TemporalType temporalType) {
            super(dataType, attributeClass, false, temporalType);
        }
    }

    /**
     * Oracle用の{@link StandardColumnType}の実装クラスです。
     *
     * @author taedium
     */
    public static class OracleSqlBlockContext extends StandardSqlBlockContext {

        /**
         * インスタンスを構築します。
         */
        protected OracleSqlBlockContext() {
            sqlBlockStartKeywordsList.add(Arrays.asList("create", "or",
                    "replace", "procedure"));
            sqlBlockStartKeywordsList.add(Arrays.asList("create", "or",
                    "replace", "function"));
            sqlBlockStartKeywordsList.add(Arrays.asList("create", "or",
                    "replace", "triger"));
            sqlBlockStartKeywordsList.add(Arrays.asList("create", "procedure"));
            sqlBlockStartKeywordsList.add(Arrays.asList("create", "function"));
            sqlBlockStartKeywordsList.add(Arrays.asList("create", "triger"));
            sqlBlockStartKeywordsList.add(Arrays.asList("declare"));
            sqlBlockStartKeywordsList.add(Arrays.asList("begin"));
        }
    }
}