cypher256's blog

Pleiades とか作った

SQL 自動生成の結合+ページング「列の定義が未確定」でハマり中

Oracle 10g。S2JDBCSQL 自動生成で leftOuterJoin で OneToMany の結合をして、ページング指定すると「ORA-00918: 列の定義が未確定です。」が発生。使い方がまずいんでしょうか? 調査中です。

List<TCmp> oyaList = jdbcManager
       .from(TOya.class)
       .leftOuterJoin("tKoList")
       .limit(10)
       .offset(2)
       .getResultList();

下記の発行されている SQL を見ると、副問い合わせで同じカラム名があるため、発生しているようです。別名となるように as を付けて SQL*Plus などで実行する問題ないようです。

SELECT
       *
   FROM
       (
           SELECT
                   temp_.*
                   ,ROWNUM rownumber_
               FROM
                   (
                       SELECT
                               T1_.ID
                               ,T1_...
                               ,T1_...
                               ,T2_.ID
                               ,T2_...
                               ,T2_...
                           FROM
                               T_OYA T1_
                                   LEFT OUTER JOIN T_KO T2_
                                       ON T2_.T_OYA_ID = T1_.ID
                   ) temp_
       WHERE
           ROWNUM <= 12
       )
   WHERE
       rownumber_ > 2
/
2008-04-11 18:39:12,781 [http-8080-Processor25] ERROR org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/hoge].[default] - サーブレット default のServlet.service()が例外を投げました
java.sql.SQLException: ORA-00918: 列の定義が未確定です。
         at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112)
         at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:331)
         at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:288)
         at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:743)
         at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:213)
         at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:796)
         at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1031)
         at oracle.jdbc.driver.T4CPreparedStatement.executeMaybeDescribe(T4CPreparedStatement.java:836)
         at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1124)
         at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3285)
         at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3329)
         at org.seasar.extension.jdbc.impl.PreparedStatementWrapper.executeQuery(PreparedStatementWrapper.java:81)
         at org.seasar.framework.util.PreparedStatementUtil.executeQuery(PreparedStatementUtil.java:46)
         at org.seasar.extension.jdbc.query.AbstractSelect.createResultSet(AbstractSelect.java:355)
         at org.seasar.extension.jdbc.query.AbstractSelect.getResultListInternal(AbstractSelect.java:208)
         at org.seasar.extension.jdbc.query.AbstractSelect.getResultList(AbstractSelect.java:170)
         at hoge.framework.User.<init>(User.java:71)
         at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
         at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
         at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
         at java.lang.reflect.Constructor.newInstance(Constructor.java:494)
         at org.seasar.framework.util.ConstructorUtil.newInstance(ConstructorUtil.java:54)
         at org.seasar.framework.beans.impl.BeanDescImpl.newInstance(BeanDescImpl.java:204)
         at org.seasar.framework.container.assembler.AbstractConstructorAssembler.assembleManual(AbstractConstructorAssembler.java:104)
         at org.seasar.framework.container.assembler.AbstractConstructorAssembler.assemble(AbstractConstructorAssembler.java:53)
         at org.seasar.framework.container.deployer.SessionComponentDeployer.deploy(SessionComponentDeployer.java:56)
         at org.seasar.framework.container.impl.ComponentDefImpl.getComponent(ComponentDefImpl.java:111)
         at org.seasar.framework.container.impl.S2ContainerImpl.getComponent(S2ContainerImpl.java:129)
         at org.seasar.framework.container.SingletonS2Container.getComponent(SingletonS2Container.java:62)

あと下記の認識があってるかも含め調査中です。

  • すべてのテーブルの主キーを id という名前にすると一番幸せになれる
  • selectBySQL を使用した場合、関連テーブルは取得できない
  • 同一トランザクション内で DB テーブルの共通項目である更新日などを保持したい。現在、jdbcManager を拡張した Dao クラスで、insert および update 内で更新日をセットしているが、ActionServlet を拡張して process メソッドの最初で日時をスレッド・ローカル(リクエスト属性とかのほうがいい?)にセットし、それを使っている。S2Tx や S2JTA などがどこかにトランザクション開始時の日時情報などを持っているか?
  • これは仕組み上当たり前かもしれませんが、Cubby の画面部品風に JSP で DB アクセスすると、JSTL でプロパティにアクセスできない
    • やるなら Beans などで Map にコピー
    • あるいはエンティティの共通親クラスに Map インタフェースを実装し、get メソッド内で指定された名前のフィールドにリフレクションでアクセスするようにしておけば、開発者は意識しなくていける気がする


SAStruts + S2JDBC でコード量はやはり激減します。SAStruts は非常に分かりやすいです。ただ、S2JDBC は 3 人ほどで調査+先行実装している結果実績ベースでは、決して初期学習工数は低くはありません。特に今回、検索画面条件項目が多く、AND や OR などを切り替え可能で、自動生成の場合は ComplexWhere が使えますが、ちょっと思考が流れるという感じにはいかなさそうです。


このあたり、最初の想定どおり selectBySQL でやってれば良かったとも思いますが、SQL 自動生成のコード量の少なさも捨てがたいです。開発方針としては、出来るだけ、色々な方法があるというのは避けたいのですが、悩みどころです。