表結合を含む SQL 照会用のネストされた Bean の作成

表結合を含む SQL 照会からデータを返す Bean のセットを作成するには、ネストされた Bean のセットで pureQuery アノテーションを使用します。 pureQuery Runtime は、ネストされた Bean のセットの実装を生成します。 この実装は SQL ステートメントでの表結合、および Bean に追加された pureQuery アノテーションに基づきます。

返したいデータの階層データ構造を定義する、ネストされた Bean のセットを作成します。 ResultSet オブジェクトの列に適合する Bean プロパティーに対する @JoinPoint アノテーションを指定すると、pureQuery Runtime が適切な実装を生成します。 pureQuery Runtime はトップレベル Bean (ResultSet オブジェクトとネストされた Bean の処理後に返される Bean) の階層構造を分析します。 生成されてデータが追加される Bean/Bean リストは、ResultSet オブジェクトの列によって更新される、@JoinPoint アノテーションを持つ Bean/Bean リストのみです。 ResultSet オブジェクトの列によってデータが追加されない Bean/Bean リストや、@JoinPoint アノテーションを持たない Bean/Bean リストはいずれも、 ヌルになるか、または Bean コンストラクター初期設定値になります。

ネストされた Bean のセットの例

次の例では、2 つの Bean が示されています。 トップレベル Bean は Department Bean です。 子 Bean は Employee Bean のリストです。

この例では、DEPARTMENT 表と EMPLOYEE 表を結合する次の SQL 照会が使用されます。 この照会では、1 つのパラメーター (部門番号) が使用されていて、その部門に属するすべての従業員が返されます。
Select D.DEPTNO, D.DEPTNAME, D.MGRNO, D.ADMRDEPT, E.EMPNO, E.FIRSTNME, E.MIDINIT, 
     E.LASTNAME, E.JOB, E.SEX, E.BIRTHDATE, E.SALARY 
   from (DEPARTMENT AS D LEFT JOIN EMPLOYEE AS E ON D.DEPTNO=E.WORKDEPT) 
   WHERE D.DEPTNO=? ORDER BY D.DEPTNO, E.LASTNAME
次の DAO インターフェース・メソッドでは、この SQL 照会が指定されています。
  @Select(sql = "Select D.DEPTNO, D.DEPTNAME, D.MGRNO, D.ADMRDEPT, E.EMPNO, 
     E.FIRSTNME, E.MIDINIT, E.LASTNAME, E.JOB, E.SEX, E.BIRTHDATE, E.SALARY 
    from (DEPARTMENT AS D LEFT JOIN EMPLOYEE AS E ON D.DEPTNO=E.WORKDEPT) 
    WHERE D.DEPTNO=? ORDER BY D.DEPTNO, E.LASTNAME")
  public Department joinTest (String deptNo);

次の 2 つの Bean は、この SQL 照会用に作成されています。 Department Bean は、結果セットの処理後に返されるトップレベル Bean です。 Department Bean は deptEmployees Bean のリストの親 Bean です。 deptEmployees Bean のリストは子 Bean のセットです。 ID 列 (@Id アノテーションを持つ Bean でのプロパティー) は deptNo です。
@Table(name = "DEPARTMENT")
public class Department
{
  @Id
  @GeneratedKey
  public String deptNo; // this is the ID of the bean and its value is generated by the database
  public String deptName;
  public String mgrNo;
  public String admrDept;
  @JoinPoint(@JoinColumn(name = "EMPNO", table = "EMPLOYEE", propertyName = "empNo"))
  public List<Employee> deptEmployees;
} 
deptEmployees Bean のリストによって返されるデータは、Employee クラスによって定義されます。
@Table(name = "EMPLOYEE")
public class Employee
{
  @Id
  @GeneratedKey
  public String empNo; // this is the ID of the bean and its value is generated by the database
  public String firstNme;
  public String midInit;
  public String lastName;
  public String workDept;
  public String job;
  public String sex;
  public java.sql.Date birthdate;
  public String salary;
}

SQL 照会と Bean に基づき、pureQuery Runtime は Bean の実装コードを生成します。

返される Java オブジェクトは、11 個の deptEmployees Bean からなる java.util.list を 含む 1 つの Department Beanです。

メソッド joinTest("D11") の呼び出しからのフォーマット設定済み結果出力。 最初の 4 列は Department Bean からのデータです。 他の列の従業員データは deptEmployees Bean からのデータです。
dept                             admr                    mid
No  deptName              mgrNo  Dept empNo    firstNme  Init lastName  job      sex birthdate  salary
D11 MANUFACTURING SYSTEMS 000060 D01  000150   BRUCE          ADAMSON   DESIGNER M   1977-05-17 55280.00
                                      000200   DAVID          BROWN     DESIGNER M   1971-05-29 57740.00
                                      200220   REBA      K    JOHN      DESIGNER F   1978-03-19 69840.00
                                      000210   WILLIAM   T    JONES     DESIGNER M   2003-02-23 68270.00
                                      000220   JENNIFER  K    LUTZ      DESIGNER F   1978-03-19 49840.00
                                      000160   ELIZABETH R    PIANKA    DESIGNER F   1980-04-12 62250.00
                                      000180   MARILYN   S    SCOUTTEN  DESIGNER F   1979-02-21 51340.00
                                      000060   IRVING    F    STERN     MANAGER  M   1975-07-07 72250.00
                                      000190   JAMES     H    WALKER    DESIGNER M   1982-06-25 50450.00
                                      200170   KIYOSHI        YAMAMOTO  DESIGNER M   1981-01-05 64680.00
                                      000170   MASATOSHI J    YOSHIMURA DESIGNER M   1981-01-05 44680.00 

ネストされた Bean のセット内における Bean のタイプ

表結合を含む SQL 照会用のネストされた Bean のセットを作成するときは、次のタイプの Bean を作成します。
トップレベル Bean
トップレベル Bean はメソッドまたはインライン API の戻り値となる Bean です。 ネストされた Bean のセットにおいてトップレベル Bean が pureQuery アノテーションとともに使用される場合、 トップレベル Bean には少なくとも 1 つの @Id アノテーションが必要となります。 親 Bean としても 1 つ以上の @JoinPoint アノテーションが含まれていなければなりません。 この Bean は親 Bean でも 子 Bean でもかまいません。 場合によっては、1 つの Bean が同一ネスト構造において親 Bean と子 Bean の両方になることがあります。
親 Bean
親 Bean には、Bean/Bean リスト (子 Bean) を参照する 1 つ以上の @JoinPoint アノテーションが含まれていなければなりません。 @Id アノテーションは親 Bean では不要です (ただし、親 Bean がトップレベル Bean の場合は @Id アノテーションは必要です)。 @Id アノテーションが含まれない親 Bean (例えば BeanB とします) は、子 Bean でもなければなりません。 BeanB とその親 Bean (BeanA とします) は @JoinPoint アノテーション内に @JoinColumn アノテーションを含んでいなければなりません。 BeanA の @JoinColumn アノテーションは、子 Bean (BeanB) の ID 列を指定します。
子 Bean
子 Bean は @JoinPoint アノテーションで親 Bean から参照されます。@Id アノテーションは子 Bean には不要です (ただし、その子 Bean がトップレベル Bean でもある場合は @Id アノテーションは必要です)。 子 Bean に @Id アノテーションが含まれない場合、親 Bean が @JoinPoint アノテーション内の 1 つ以上の @JoinColumn アノテーションによって、この Bean の ID 列を指定する必要があります。

ネストされた Bean のセットにおける ID 列

pureQuery Runtime は、ネストされた Bean のセットを生成して、 そのセットにデータを追加するときに、Bean の親に属する Bean に対する参照に含まれる @JoinPoint アノテーションを使用するか、 またはその Bean 自体に含まれる @Id アノテーションを使用します。 このアノテーションは、親 Bean に 子 Bean をリンクする ID 列を定義します。 例えば、pureQuery Runtime は ID 列を使用して、親 Bean の Bean リストをいつ生成するのかを決定します。

例えば、EMPLOYEE 表に、ID 列になることができる列が複数含まれているとします。 ID 列として定義できる列は従業員番号および従業員社会保障番号の列です。 ID 列は表の主キーにできますが、表のキーである必要はありません。 データベース表で複合キーを作成できるように、複数の列を組み合わせて 1 つの複合キーにすることができます。 EMPLOYEE 表の ID 列として名前の列を選択することはお勧めできません。 同じ名前の従業員が複数存在する可能性があるためです。

pureQuery Runtime は、ResultSet オブジェクトを処理するときに、ID 列情報や他の Bean 情報を使用します。 表結合を含む SQL 照会の場合、ResultSet オブジェクト内のデータを Bean の階層構造内にマップすることが難しくなることがあります。 処理中に問題を引き起こす可能性のある特性をいくつか以下に示します。
  • データの順序がソートされていない可能性がある。
  • データベース表内のデータの順序を保持しなければならない場合がある。
  • ResultSet 内のデータが重複している。 これは、データベースにおいてデータに 1 対多の関係が含まれている場合に発生します。 例えば、1 つの部門には複数の従業員がいます。 ResultSet には、部門表と従業員表を結合した結果として生成されるデータが含まれる可能性があります。 部門情報は、部門内の従業員ごとに繰り返されます。

pureQuery Runtime を使用すれば、正しいデータのセットが正しい順序で Bean に追加されます。 例えば、ResultSet データがソートされていないため、ネストされた Bean の構造に正確にマップされない可能性があります。 pureQuery Runtime は、以前の ResultSet の処理時に作成された Bean に含まれている ResultSet データを処理し、 適切なデータを正しい順序で Bean に追加できます。

ネストされた Bean のセットの作成規則

複雑な結合述部を持つ SQL ステートメントから結果を返すネストされた Bean のセットを作成するには、適切なアノテーションを使用して、より複雑なネスト構造を作成します。 正しい結果を返す Bean のセットを作成するには、次の規則に従う必要があります。 有用な Bean のセットを生成するには、SQL 照会または Bean を変更しなければならない場合があります。

  1. ResultSet オブジェクトにおける各列の列名は、親 Bean または任意の子 Bean における単一プロパティーにマップされなければなりません。
    注: 複数の列が特定のプロパティーにマップされ、その列が常に同じデータを返す場合、このマッピングが問題になることはありません。 1 つ以上の列がヌルになる可能性がある場合は、問題が発生することがあります。 例えば、ある列が、表結合を含む SQL 照会から NULL を返す可能性があるとします。 その照会には WHERE 節または ON 節が含まれている可能性がありますが、2 つの表の間に一致するキーが見つかりません。

    この問題は、SQL 照会で別名を指定することによって解決できます。 複数の列を持つ Bean 内のプロパティー上の「表名」を使用して、@Column、@Table、または @JoinColumn アノテーションを指定することもできます。

  2. 同じ Bean がネストされた Bean の構造に複数回含まれる場合、その Bean に対する参照は、初出以外はすべて無視されます。 最初のインスタンスのみを使用することで、pureQuery Runtime がネストされた Bean のセットを処理する際に、無限再帰が発生する可能性を防ぐことができます。 複数の類似した Bean を作成するには、@JoinPoint オプション・エレメント columnPrefix、 元の Bean のサブクラスと SQL 照会の列別名、または、@JoinPoint アノテーション/@JoinColumn アノテーションを使用します。
  3. @JoinColumn 必須エレメント name は、子 Bean における基本プロパティー名と一致しなければなりません。 一致しない場合は、子 Bean において一致する基本プロパティーの名前を使用してオプション・エレメント propertyName を指定する必要があります。

    基本プロパティー名 は Bean プロパティーの名前ですが、 この Bean プロパティーでは、ResultSet オブジェクトへのこの Bean プロパティーのマッピングを変更するような既存のアノテーションは使用されません。 フィールドに対しては、基本プロパティー名は Java 変数名です。 get メソッド、set メソッド、または is メソッドに対しては、 基本プロパティー名は、getsetis のいずれの接頭部も持たない メソッド名です (最初の文字は小文字に設定されます)。 次の例の ResultSet 列ラベル WORKDEPT には大/小文字の区別はありません。

    次の例の get メソッドにおける基本プロパティー名 workDepartment では、大/小文字が区別されます。
    @Column (name = "WORKDEPT")
    public String getWorkDepartment (); 

    @JoinColumn アノテーションは、Bean/Bean リスト上の @JoinPoint アノテーションの内側に指定できます。 その場合、指定された name エレメントと table エレメントは、 子 Bean の @Id アノテーション (および関連 @Column アノテーションが指定されている場合はそのすべての関連 @Column アノテーション) で定義されている 列ラベルとオプションの表名をオーバーライドします。

    1 つの子 Bean に複数の @Id アノテーションを指定できます。 その場合、@JoinColumn アノテーション必須エレメント name は、子 Bean における基本プロパティー名と一致しなければなりません。 名前が一致しない場合は、子 Bean において一致する基本プロパティーの名前を使用して オプション・エレメント propertyName を指定する必要があります。

    SQL 照会における EMPNO の別名が EMPNUM になるように前の例を変更した 場合、@JoinColumn アノテーション内の propertyName エレメントは不要です。 子 Bean は @Id アノテーションを 1 つのみ持ち、pureQuery Runtime によってマップ先のプロパティーが判別されます。 次の Department Bean の @JoinPoint アノテーションを変更します。
    @JoinPoint(@JoinColumn(name = "EMPNO", table = "EMPLOYEE", propertyName = "empNo"))
      public List<Employee> deptEmployees;
    この @JoinPoint アノテーションは、次のように指定できます。
      @JoinPoint(@JoinColumn(name = "EMPNUM", table = "EMPLOYEE", propertyName = "empNo"))
       public List<Employee> deptEmployees;
    Employee 子 Bean の ID 列を empNo から workDept に 変更する場合は、PropertyName エレメントを次のように指定します。
      @JoinPoint(@JoinColumn(name = "WORKDEPT", table = "EMPLOYEE", propertyName = "workDept"))
      public List<Employee> deptEmployees;
  4. @Table アノテーションはクラス・レベルで使用されます。 このアノテーションでは、表名が指定されないすべてのプロパティーに追加されるデフォルトの表名が指定されます。 この表名をオーバーライドして、任意の表名に適合するプロパティーを指定できます。
  5. 子 Bean には、ネストされた Bean のセットの一部と見なされる @Id アノテーションは必要ありません。 @JoinPoint アノテーションでは子 Bean の ID 列を指定できます。 このように @Id アノテーションで ID 列を指定できるのは、その呼び出しにおけるその親 Bean に対してのみです。

    子 Bean は単一 Bean または Bean リストでなければなりません。 また、子 Bean には @JoinPoint アノテーションが必要です。 @JoinPoint アノテーションは、子 Bean/Bean リストを定義する親 Bean プロパティー上になければなりません。

  6. ResultSet オブジェクトにおける各列は、正しい親 Bean と子 Bean におけるプロパティーにのみマップされなければなりません。

    このマッピングの例として、DEPARTMENT 表 (主キーとなる DEPTNO 列がある) と PROJECT 表 (この表にも DEPTNO 列がある) があります。 PROJECT 表の DEPTNO 列は、DEPARTMENT 表の外部キーです。 ResultSet オブジェクトにおける列と、プロパティーとの間でデフォルトで一致するのは、この列ラベルです。 この例では、DEPARTMENT 表と PROJECT 表を結合した結果、DEPTNO という列ラベルを持つ列が 2 つ作成されることになります。 親と子の Bean 構造でこれらの列に別々の情報が必要な場合は、これらの Bean にアノテーションを追加できます。 このアノテーションでは、表名を使用して列を固有にすることができます。 また、別名を追加して固有の列ラベルを作成することで、SQL ステートメントを変更することもできます。 ただし、通常、SQL 照会では、複数の列に同一データが含まれる場合は、結合を使用して列を 1 つのみ返すようにします。 この場合、1 つの列で複数のプロパティーを変更できます。

    データ・アクセス・オブジェクト (DAO) スタイルのメソッドでは、ResultSet オブジェクトの同じ列によって複数のプロパティーが更新されているという警告が 次のように表示されます。
    • 生成されたメソッド (同じ列を使用するもの) についての警告が表示されます。 IBM® Data Studio を使用している場合、この警告は実装の生成後に出力されます。 また、ジェネレーター・ログ・ファイルが存在し、トレース・レベルが java.util.logging.Level.WARNING またはより詳細なレベルに設定されている場合、 この警告はそのジェネレーター・ログ・ファイルにも記録されます。
    • pureQuery Generator は、生成されたネストされた Bean の ResultHandler の上にコメント・ブロックを作成します。 コメントでは、生成されたどのハンドラーが ResultSet 列を使用するのかが示されます。 また、使用されない ResultSet 列も示されます。

    インライン・メソッド・スタイルでは、同じ ResultSet 列によって複数のプロパティーが更新されているという警告が出力されます。 pureQuery Runtime ログ・ファイルが存在し、トレース・レベルが java.util.logging.Level.WARNING またはより詳細なレベルに設定されている場合、 この警告は pureQuery Runtime ログ・ファイルに記録されます。

  7. @JoinPoint アノテーションは、子 Bean が親 Bean に結合されるときは必ず必要です。 次のいずれかの記述が当てはまる場合を除き、@JoinPoint アノテーション内に @JoinColumn アノテーションを 1 つ指定する必要があります。
    • 子 Bean/Bean リストに、子 Bean の ID 列を完全に定義する @Id アノテーションが 1 つ以上含まれる。
    • 子 Bean の ID 列のプロパティー (およびオプションの列ラベルと表名) により、ResultSet オブジェクトを作成するために 実行される SQL ステートメントに対して列が正しく指定されている。 この場合、エレメントなしで @JoinPoint アノテーションのみを指定できます。

ネストされた Bean のランタイム処理

以下のステップは、pureQuery Runtime が ResultSet オブジェクトのデータでオブジェクトを生成するときにネストされた Bean のセットを処理する方法について 説明したものです。
  1. 返される Bean (トップレベルの親 Bean) から解析が開始されます。 この Bean の各プロパティーが検査されて、それぞれのプロパティーが ResultSet オブジェクトによって更新されているかどうかが判別されます。 プロパティーが更新されていて、@JoinPoint アノテーションが含まれている場合、 そのプロパティーは検査されて、そのプロパティーが Bean/Bean リストを参照するかどうかが判別されます。 そのプロパティーが Bean/Bean リストを参照する場合、その Bean は親 Bean に対する子 Bean リストに追加されます。 親 Bean 内のすべてのプロパティーが処理されてから、pureQuery Runtime は見つかった子 Bean を処理します。 Bean 内でプロパティーが検査される順序は決まっていません。
  2. 親 Bean 内のすべてのプロパティーが検査された後で、子 Bean のリストが検査されます。 リストに 1 つ以上の子 Bean が含まれる場合、リスト内の最初の Bean は親 Bean であるかのように処理されます。
  3. 元の親 Bean 内で最初の子 Bean が見つかり、子 Bean のリストが作成された後で、pureQuery Runtime は元の親 Bean の次の子 Bean を処理します。 別の子 Bean が存在する場合、その子 Bean はステップ 2 と同じようにして処理されます。

    元の親 Bean の子 Bean がすべて処理されてから、すべての子 Bean が処理されて、その子 Bean にさらに子 Bean があるかどうかが判別されます。

pureQuery Runtime は、子 Bean クラスが初めて処理されるのか、または Bean に再帰的参照が含まれているのかを判別できます。 ネストされた Bean 構造を pureQuery Runtime が解析したときに、その構造に含まれる Bean が既に解析済みであることが判明すると、pureQuery Runtime は その子 Bean の解析を停止します。

注: 同じ子 Bean が複数含まれている親 Bean を使用することはサポートされていません。 このような状態では、pureQuery Runtime は、その子 Bean のうちの 1 つにのみ情報を設定します。 その子 Bean の固有プロパティー・アノテーションを使用してサブクラスを指定できます。 また、重複クラスごとに親 Bean の @JoinPoint アノテーション内の columnPrefix エレメントを使用できます。

また、Bean に対する再帰的参照は、pureQuery Runtime が子 Bean のプロパティーにデータを追加する方法にも影響します。 データが追加されるのは、Bean の最初のインスタンスのみです。 その Bean に対する後続の参照は、別の columnPrefix エレメントが定義されている @JoinColumn アノテーションがその参照に含まれていない限り 無視されます。 単一 Bean 内のプロパティーの処理順序は決まっていないため、その子 Bean の処理順序も決まっていません。 ただし、子 Bean はネストのレベル順に処理されます。 例えば、親 Bean に直接ネストされている Bean は、次のネスト・レベルにある Bean より前に処理されます。

columnPrefix エレメントについては、@JoinPoint アノテーション資料を参照してください。


フィードバック