kuniku’s diary

はてなダイアリーから移行(旧 d.hatena.ne.jp/kuniku/)、表示がおかしな箇所はコメントをお願いします。記載されている内容は日付およびバージョンに注意してください。直近1年以上前は古い情報の可能性が高くなります。

s2jdbcでPostgreSQLの幾何データ型を使う

初めて、s2jdbc-gens2jdbcを使用してみた。

変更履歴
2009-08-26 「最低限やること」「PostgreDialectを拡張オーバーライド」を修正

postgreSQLのデータ型

PostgreSQLにはデータ型として幾何データ型がある。
その幾何データ型は、S2JDBCが標準でサポートしている永続プロパティの型とは異なるため、
s2jdbc-genデフォルトで作成した場合は、String型の扱いとなる。

それを、postgreSQLjdbcドライバで用意している型にするには?ということでちょっと模索してみた。

PostgreSQL JDBCドライバが返すメタデータのように、point、line、box、path、polygon、circleといった型をjdbcドライバが用意する型として扱いたい。

Seasar2メーリングリストを見ると

Seasar-user:11711 S2JDBC 型マッピングのカスタマイズについて
Seasar-user:11718 Re: S2JDBC 型マッピングのカスタマイズについて

といった情報があるので参考にしてみた。

  • 使用している主なライブラリのバージョン
    • postgresql-8.3-605.jdbc4.jar(jre1.6用)
    • s2-extension-2.4.39.jar
    • s2-framework-2.4.39.jar
    • s2-tiger-2.4.39.jar
    • s2jdbc-gen-2.4.39.jar

s2jdbcの処理

SQLの結果をエンティティにセットする

org.seasar.extension.jdbc.query.AbstractQuery<S extends Query<S>> #handleResultSet(ResultSetHandler handler, ResultSet rs)

ret = handler.handle(rs);

で、エンティティクラスのプロパティにセットされる。

エンティティ(カラム)に対応する型情報は、
org.seasar.extension.jdbc.dialect.PostgreDialect#getValueType(PropertyMeta propertyMeta)
により判断される。

ということで、point型を試しにやってみる。

最低限やること

  • PostgreDialectを拡張
    • #getValueType(PropertyMeta propertyMeta)をオーバーライド
      • 自動生成で実行されるときに使われる?
    • #getValueType(Class clazz, boolean lob, TemporalType temporalType) をオーバーライド
      • SQLファイルに記述したときに使われる?
      • 幾何データ型で検索する場合など、自動生成で使用することは まれ だと思われるのでこちらを主に使うようになるのではないかな。
  • point、line、boxといった型に対応したValueTypeを実装したクラスを作成する
    • getValueTypeで、pointやline、boxそれぞれにあったValueTypeを返却する。
  • エンティティでpoint型を定義
    • jdbc内の型でプロパティを宣言する(自動生成されたStringから変更)
  • dialectを変更する

PostgreDialectを拡張して、オーバーライド

    @Override
    public ValueType getValueType(PropertyMeta propertyMeta) {
        final Class<?> clazz = propertyMeta.getPropertyClass();
        if (propertyMeta.isLob()) {
            return super.getValueType(propertyMeta);
        } else {
            final ValueType valueType = getGeometryValueType(clazz);
            if (valueType != null) {
                return valueType;
            }
        }

        final ValueType valueType = getValueTypeInternal(clazz);
        if (valueType != null) {
            return valueType;
        }
        return super.getValueType(propertyMeta);
    }

    @Override
    public ValueType getValueType(Class<?> clazz, boolean lob, TemporalType temporalType) {

        ValueType valueType = getGeometryValueType(clazz);
        if (valueType != null) {
            return valueType;
        }
        return super.getValueType(clazz, lob, temporalType);
    }

    protected ValueType getGeometryValueType(final Class<?> clazz) {

        if (clazz == PGpolygon.class) {

            System.out.println("★polygon---★");
            return PostgresValueTypes.PGPOLYGON;

        } else if (clazz == PGbox.class) {

            System.out.println("■box---■");
            return PostgresValueTypes.PGBOX;

        } else if (clazz == PGpoint.class) {

            System.out.println("▲point---▲");
            return PostgresValueTypes.PGPOINT;
        }
        return null;
    }

point型に対応したValueTypeの作成

public class PGpointType extends AbstractValueType {

    public PGpointType() {
        super(Types.OTHER);
    }

    @Override
    public void bindValue(PreparedStatement ps, int index, Object value) throws SQLException {
        if (value == null) {
            setNull(ps, index);
        } else {
            ps.setObject(index, value);
        }

    }

    @Override
    public void bindValue(CallableStatement cs, String parameterName, Object value)
            throws SQLException {
        throw new UnsupportedOperationException("未実装");
    }

    @Override
    public Object getValue(ResultSet resultSet, int index) throws SQLException {
        return (PGpointType) (resultSet.getObject(index));
    }

    @Override
    public Object getValue(ResultSet resultSet, String columnName) throws SQLException {
        return (PGpointType) (resultSet.getObject(columnName));
    }

    @Override
    public Object getValue(CallableStatement cs, int index) throws SQLException {
        throw new UnsupportedOperationException("未実装");
    }

    @Override
    public Object getValue(CallableStatement cs, String parameterName) throws SQLException {
        throw new UnsupportedOperationException("未実装");
    }

    @Override
    public String toText(Object value) {
        if (value == null) {
            return BindVariableUtil.nullText();
        }
        PGpointType var = (PGpointType) (value);
        return BindVariableUtil.toText(var);
    }
}

PostgreSQL専用の値タイプのファクトリ

public class PostgresValueTypes {
	public final static ValueType PGPOINT = new PGpointType();
}


テーブルのエンティティ

import org.postgresql.geometric.PGpoint;
@Entity
@Generated(value = {"S2JDBC-Gen 2.4.39", "org.seasar.extension.jdbc.gen.internal.model.EntityModelFactoryImpl"}, date = "2009/08/19 12:00:00")
public class TblXxxEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    /** pointxyプロパティ */
    // @Column(length = 2147483647, nullable = true, unique = false)
    // デフォルトだとString型で定義される
    public PGpoint pointxy;

}

作成したdialectを使用するように設定
s2jdbc.diconの設定でPostgreDialectを拡張したクラスを使うように設定

<components>
	<include path="jdbc.dicon"/>
	<include path="s2jdbc-internal.dicon"/>
	<component name="samplePostgreDialect" class="sample.jdbc.dialect.SamplePostgreDialect">
	</component>

	<component name="jdbcManager" class="org.seasar.extension.jdbc.manager.JdbcManagerImpl">
		<property name="maxRows">0</property>
		<property name="fetchSize">0</property>
		<property name="queryTimeout">0</property>
		<property name="dialect">samplePostgreDialect</property>
	</component>
</components>

やってみたは良いが、、【課題】
とりあえず、やってみたが どうなんだろ。

PGpointType extends AbstractValueType のように、polygonやboxもそれぞれ定義する必要がある。
ValueTypeの実装が かなり不安だ。。。

#getValue(ResultSet resultSet, int index)
#getValue(ResultSet resultSet, String columnName)
は、何とかなるが、
#bindValue()
#getValue(CallableStatement cs, int index)
#getValue(CallableStatement cs, String parameterName)
などが どういった場合に呼ばれるのか?

#toText(Object value)
については、それぞれの幾何データ型が カンマ区切りや()付きで格納されていそうなので
それを考慮する必要がありそうかな。