taediumの日記

2008-12-25

[][] HudsonでS2JDBCの結合(統合)テストを自動化

S2JDBCでは、次のデータベースを使ったテストを行っています。

これまでは手動で動作確認していましたが、Hudsonで自動テストができるようになりました。ちょっとずつテストコードを作ってきたことが報われた?ような気がして何だかうれしいです。

直近で(#113〜#130あたり)でビルドがたくさん失敗しているけど気にしない(これは、S2JDBCに何か問題があったということではなく、テストのための設定に試行錯誤が必要な箇所があったからです)。これからは安定すると思います。

しばらく前に、Javaフレームワークの評価が流行っていましたが、どういうテストが行われているかという観点を評価軸に加えるのもおもしろいかもしれませんね。

そういえば、Hudsonでは自動化できていませんが、MS SQL Server 2005を使ったテストも行っています。

2008-11-30

[][] S2JDBCのwhereメソッドのin条件にListを

JavaEE勉強会の帰り道、S2JDBCのwhereメソッドでin条件に配列ではなくListを渡せるようにしてほしいという意見を聞きました。

「対応しているはずでは?」と答えてしまいましたが、SimpleWhre、Condition、Operationsのいずれでも対応していないですね。

現状は、Listを配列に変換してから指定する必要があります。

SimpleWhereの場合は、

where(new SimpleWhere().in("employeeNo", list.toArray()))

というように、toArray()を呼び出して配列に変換するだけなのでまぁいいのですが、Operations(とCondition)はタイプセーフなので、

where(in(employeeNo(), list.toArray(new Integer[list.size]{}))

と、引数ありのtoArray()を呼び出さないといけないですね。

これは面倒なので、確かにListをそのまま渡せたほうが便利そうです。

2008-08-07

[][] S2JDBCのDBマイグレーション機能

Seasar Conference 2008 AutumnA1のセッションS2JDBCのDBマイグレーション機能についてデモを交えつつ何かしゃべる予定です。いろんな方の意見を聞いてみたいなと思っているので、気軽に声をかけてください。よろしくお願いします。

2008-07-06

[][] エンティティのJavaコードとDDLの生成機能の案

もともとの案としては、Generation Gapパターンを使って親クラスと子クラスのコードを生成して子クラスのコードを好きに変更してもらおうと思っていました。例えばAddressテーブルからエンティティを生成する場合はこんな感じ。

親クラスのコード
@MappedSuperclass
public abstract class AbstractAddress {

    @Id
    @GeneratedValue
    @Column(nullable = false)
    public Integer addressId;

    @Column(length = 20, nullable = true)
    public String street;

    @Version
    @Column(nullable = true)
    public Integer version;
}
子クラスのコード
@Entity
@Table(schema = "PUBLIC")
public class Address extends AbstractAddress {
}

でも、例えば、エンティティを基にSTREETカラムの長さ100にしてDDLを生成したいなぁというときは、子クラスのコードでそれは表現できないので親クラスのコードを触ることになるんですよね。

親クラスのアノテーションの属性を変更
    @Column(length = 100, nullable = true)
    public String street;

これだと、Generation Gapパターンのありがたみがない(親クラスのコードに手を入れているので)。

ということで、わりきってGeneration Gapパターンをつかわずに継承関係を持たないエンティティのコードを出力しようかなぁと思います。

Javaコードを出力するのは最初の1回だけであとはコードを直していくと考えれば親と子でクラスが分かれている必要性がないんですよね。

2007-12-17

[][] ストアドプロシージャで複数ResultSetを取得

S2JDBCで地味に便利なのがストアド周りです。

たとえば、Oracleで3つのカーソルを返すストアドプロシージャを用意します。

create or replace PROCEDURE PROC 
( cur1 OUT SYS_REFCURSOR, 
  cur2 OUT SYS_REFCURSOR,
  cur3 OUT SYS_REFCURSOR
) AS
BEGIN
  OPEN cur1 FOR SELECT * FROM EMPLOYEE WHERE EMPLOYEE_ID < 10; 
  OPEN cur2 FOR SELECT * FROM DEPARTMENT WHERE DEPARTMENT_ID < 10; 
  OPEN cur3 FOR SELECT * FROM ADDRESS WHERE ADDRESS_ID < 10; 
END PROC;

これはこんな感じで呼び出せます。

public void testProcedure() throws Exception {
    Param param = new Param();
    jdbcManager.call("PROC", param).execute();

    assertNotNull(param.employees);
    assertTrue(param.employees.size() > 0);
    assertNotNull(param.departments);
    assertTrue(param.departments.size() > 01);
    assertNotNull(param.addresses);
    assertTrue(param.addresses.size() > 0);
}

public static class Param {

    @ResultSet
    public List<Employee> employees;

    @ResultSet
    public List<Department> departments;

    @ResultSet
    public List<Address> addresses;
}

ポイントはパラメータ用のクラスに結果セットに対応するListを用意して@ResultSetをつけておくこと。一度に複数の結果セットを取得できちゃうのが便利。ストアド好きの人は結構うれしんじゃないでしょうか。

ちなみに、RDBMSごとに結果セットを返すストアドの書き方はぜんぜん違うのです(結果セットを返さなくてもちがうけど)。S2JDBCのテストをするときに、OracleSQL ServerDB2MySQLPostgreSQLと5つのDB用のストアドプロシージャを書いたのですが、はっきり言ってどれも覚えてない。。。。SVNにコードがあるから見ればわかりますけど。

次のストアドプロシージャのコードをぱっと見てどのRDBMSのものかわかったらすごいです。

CREATE OR REPLACE PROCEDURE PROC_RESULTSETS
( empCur OUT SYS_REFCURSOR, 
  deptCur OUT SYS_REFCURSOR, 
  employeeId IN NUMERIC, 
  departmentId IN NUMERIC
) AS
BEGIN
  OPEN empCur FOR SELECT * FROM EMPLOYEE WHERE employee_id > employeeId ORDER BY employee_id;  
  OPEN deptCur FOR SELECT * FROM DEPARTMENT WHERE department_id > departmentId ORDER BY department_id;
END PROC_RESULTSETS;
/
CREATE PROCEDURE dbo.PROC_RESULTSETS
    @employeeId int,
    @departmentId int
AS
BEGIN
    SELECT * FROM EMPLOYEE WHERE employee_id > @employeeId ORDER BY employee_id;  
    SELECT * FROM DEPARTMENT WHERE department_id > @departmentId ORDER BY department_id;
END
GO
CREATE PROCEDURE PROC_RESULTSETS(
  IN employeeId INTEGER,
  IN departmentId INTEGER)
DYNAMIC RESULT SETS 2
BEGIN
  DECLARE c_emp CURSOR WITH RETURN FOR
    SELECT * FROM EMPLOYEE WHERE employee_id > employeeId ORDER BY employee_id;
  DECLARE c_dept CURSOR WITH RETURN FOR
    SELECT * FROM DEPARTMENT WHERE department_id > departmentId ORDER BY department_id;
  OPEN c_emp;
  OPEN c_dept;
END@
CREATE PROCEDURE PROC_RESULTSETS(
  IN employeeId INTEGER,
  IN departmentId INTEGER)
BEGIN
    SELECT * FROM EMPLOYEE WHERE employee_id > employeeId ORDER BY employee_id;  
    SELECT * FROM DEPARTMENT WHERE department_id > departmentId ORDER BY department_id;
END
/
CREATE OR REPLACE FUNCTION PROC_RESULTSETS(
  empCur OUT refcursor,
  deptCur OUT refcursor,
  employeeId IN INTEGER,
  departmentId IN INTEGER)
AS $$
BEGIN
  OPEN empCur FOR SELECT * FROM EMPLOYEE WHERE employee_id > employeeId ORDER BY employee_id;
  OPEN deptCur FOR SELECT * FROM DEPARTMENT WHERE department_id > departmentId ORDER BY department_id;
  RETURN;
END;
$$ language plpgsql;

答えは、上から順にOracleSQL ServerDB2MySQLPostgreSQLとなります。カーソルの定義の仕方、パラメータのタイプ(INとかOUT)を書く位置、区切り文字などもそれぞれ異なっていてとても覚えられないですよねー。

[][] S2JDBCでHibernateのiterate()相当の処理

S2Jdbc で1件ずつフェッチできれば、それで決定なのになぁ。

letter: [Java

大量データを検索して処理したい時に、List<Bean>だとOutOfMemoryErrorが発生させてしまう場合がある。1行づつデータを取ってくるIteratorもほしい気がする。例えばこんな感じ。

2007-11-25 - A.R.N [開発

S2JDBCでHibernateのiterate()やscroll()のように1件ずつ処理したい場合ですが、そういうときは最初にidのリストや配列を取得して、そのあと1件ずつ取得して処理するといいと思います。

1+NのSELECTが発生してパフォーマンスが悪いんじゃん?と思うかもしれませんが、S2JDBCは同じトランザクション内でPreparedStatementをキャッシュしているのでパフォーマンスは結構いいです。N件のSELECTはすべて同じPreparedStatementで処理されることになります。

コードはこんな感じになります。

テストコード
public void testIterate() throws Exception {
    // 1件目のSQL
    List<Integer> idList =
        jdbcManager
            .selectBySql(
                Integer.class,
                "select employee_id from employee where employee_id < 100")
            .getResultList();

    for (Integer id : idList) {
        // N件のSQL
        Employee e =
            jdbcManager.from(Employee.class).id(id).getSingleResult();
    }
}
実行されるSQL
select employee_id from employee where employee_id < 100

SELECT t1_.employee_id,
  t1_.employee_no,
  t1_.employee_name,
  t1_.manager_id,
  t1_.hiredate,
  t1_.salary,
  t1_.department_id,
  t1_.address_id,
  t1_.version
FROM employee t1_
WHERE t1_.employee_id = 1

SELECT t1_.employee_id,
  t1_.employee_no,
  t1_.employee_name,
  t1_.manager_id,
  t1_.hiredate,
  t1_.salary,
  t1_.department_id,
  t1_.address_id,
  t1_.version
FROM employee t1_
WHERE t1_.employee_id = 2

...(idの値が違うだけのSQLがつづく)

たいした手間ではないですが、IDのリストを取得するクエリがちょっと面倒くさいと感じちゃうかもしれませんね。

Seasarの次のバージョン2.4.19では、専用のiterate()処理が含まれるかもしれないです。