CustomizeEntity

Sql2Entityで自動生成される CustomizeEntity についてのページです。

CustomizeEntityとは?

テーブル対応のEntityを DomainEntity と呼ぶのに対し、SQL対応のEntity を CustomizeEntity と呼びます。CustomizeEntityは SQLのselect句と同じプロパティ構造 を持ちます。

例えば、"select * from MEMBER" というようなSQLの結果はDomainEntityで受け取ることができますが、 "select MEMBER_ID, MAX(xxx) from ... group by MEMBER_ID" というようなSQLの結果はDomainEntityでは受け取れないため、そのSQLのselect句と同じ構造を持ったCustomizeEntityで受け取るようにします。

CustomizeEntityの自動生成

Sql2Entityで自動生成される

CustomizeEntityは、Sql2Entityタスクで自動生成されます。 出力先ディレクトリと出力クラスのパッケージは、デフォルトではGenerateタスクで自動生成されるクラスと同じで、Entity のパッケージ配下の customize パッケージに出力されます。

外だしSQLに、このSQLに対応するCustomizeEntityを生成したいというマーク (CustomizeEntity) を記述し、Sql2Entityタスクを実行することでCustomizeEntityが自動生成されます。 (CustomizeEntityの自動生成対象となる外だしSQLは、Sql2Entityタスクで実際のDBに対して実行されるため、外だしSQLは必ず "2Way-SQL" で記述されていることを前提とします)

自動生成によるメリット

外だしSQLで利用する戻り値の型は、DomainEntity でなく、そのSQL独自の構造を持ったEntity であって欲しい場合が多々あります。そのEntityを手動で作成すると面倒なのはもとより "プロパティ名とSQLで定義したカラム名が食い違うバグ" も発生します。 よって、DBFluteではSql2Entityタスクを利用して、SQL文そのものから定義すべきプロパティ名を導出して自動生成し、そのような問題を解決します。 手動作成の面倒な点もなくなり、入力ミスなどによる名称定義の食い違いでのバグも一切なくなります。

古いクラスは削除される

外だしSQLのどこにも記述されていない既存の古いCustomizeEntityは(ファイルシステム上から)削除されます。 古いCustomizeEntityを利用しているプログラムはコンパイルエラーとして検知できます。

CustomizeEntityマーク

CustomizeEntityの生成を示すマークは、SQLの行コメント形式 "-- " で、#df:entity# という形で記述します。これを CustomizeEntityマーク と呼びます。 例えば、SQLファイルの名前が MemberBhv_selectSimpleMember.sql なら、CustomizeEntityのクラス名は SimpleMember となります。 (SQL業務名がそのまま名前となります)

e.g. "MemberBhv_selectSimpleMember.sql" に対応する CustomizeEntity を生成 @OutsideSql
-- #df:entity#
select ...
  from ...

CustomizeEntity のクラス名を明示的に指定することもできます。SQLの行コメントで、"#[Entityのクラス名]#" という形で記述します。主に、他のSQLで CustomizeEntity を再利用するなど、SQL名に依存しない名前を付けたいようなときに利用します。

e.g. SimpleMemberという名前のCustomizeEntityを生成 @OutsideSql
-- #SimpleMember#
select ...
  from ...

CustomizeEntityマークは、基本的に select文 に対して記述することを想定しています。 update文 に対しての記述は意味のないことです。

CustomizeEntityのオプション

PKの設定

生成されるCustomizeEntityが持つ情報として、そのSQL(の結果レコード)をユニークに識別するPK を設定することができます。そうすることでは、CustomizeEntityの equals()メソッド や hasPrimaryKey()メソッド にて指定されたPKを利用するようになります。また、外だしSQLの LoadReferrer を利用する場合はこのPKマークの指定が必須です。

SQLの行コメント で、*[主キー]* という形で記述します(複数指定する場合はカンマ区切りで指定)。これを CustomizeEntityのPKマーク と呼びます。

e.g. MEMBER_IDをPKとして扱いたい場合 @OutsideSql
-- #df:entity#
-- *MEMBER_ID*
select MEMBER_ID, MAX(...), ...
  from PURCHASE
    left outer join ...
 group by MEMBER_ID

ここでは、元のカラム名ではなく、検索結果上で利用される名前を指定します。例えば、エリアス名が付いている場合は、エリアス名を指定します。

e.g. エリアス名が付いているのでPK_IDをPKマークに指定 @OutsideSql
-- #df:entity#
-- *PK_ID*
select MEMBER_ID as PK_ID, MAX(...), ...
  from PURCHASE
    left outer join ...
 group by MEMBER_ID

メタデータから該当カラムの対応するテーブルの情報が取得できない DBMS のときのために、PKカラムに関しては、このPKマークにて関連テーブルを明示的に指定することができます(他の DBMS で設定しても問題はありませんが特に意味はありません)。外だしSQLの LoadReferrer では対応テーブルの情報が必要なため(本来、メタデータから自動解決される)、単にJavaDocコメントでの表示だけの話でなく、そういった DBMS の場合においての補完機能となります。@since 0.9.7.5

e.g. 指定したPKの関連テーブルを明示的に指定 @OutsideSql
-- #df:entity#
-- *MEMBER.MEMBER_ID*
select MEMBER_ID, MAX(...), ...
  from PURCHASE
    left outer join ...
 group by MEMBER_ID

コラム:SQLのユニーク性の明示

CustomizeEntityを利用してBehaviorから更新処理を行うというようなことはないので、LoadReferrer を使わないのであれば、実際にPKの設定が必要になる場面はあまりないかもしれません。 ですが、"外だしSQLは結果のユニーク性" がわかりづらいものになりがちで、そのような場合にある種 "ドキュメントという位置付け" でそのSQLのユニーク性を宣言しておくことは、後でそのSQLをメンテナンスする人にとってはとてもありがたいことでしょう。 (SQLが)複雑でわかりにくいかもとふと思ったときに、このPKの設定をしておくと良いでしょう。

CursorHandling

CustomizeEntityマークのオプション として、TypeSafeCursor を生成することができます。TypeSafeCursorのオプションが指定されている場合はCustomizeEntityは生成されません。

e.g. TypeSafeCursorを生成 @OutsideSql
-- #df:entity#
-- +cursor+
select ...
  from ...

ScalarHandling

CustomizeEntityマークのオプション で、単一のカラム を String や Integer などのスカラ型で扱うようにすることができます(@since 0.9.8.0)。 CustomizeEntityマークに加えて、SQLの行コメントで "+scalar+" と指定すると Entity は自動生成されず、select句に定義されている単一のカラムに対応するデータ型(スカラ型)が戻り値Entityの型として TypeParameterBean に関連付けられます。

e.g. スカラ型のオプションを指定 @OutsideSql
-- #df:entity#
-- +scalar+
select MEMBER_NAME
  from MEMBER

フリースタイル形式では単に第三引数にスカラ型を指定すれば良いだけなので、これは TypedParameterBean に対してのみ有効のオプションです。 (ちなみに、フリースタイル形式は、1.1より outside().traditionalStyle() という呼び出しに変わっています)

DomainHandling

CustomizeEntityマークのオプション で、BehaviorQuery に対応する DomainEntity 型で扱うようにすることができます(@since 0.9.8.1)。 CustomizeEntityマークに加えて、SQLの行コメントで "+domain+" と指定すると Entity は自動生成されず、BehaviorQuery に対応する DomainEntity が戻り値Entityの型として TypeParameterBean に関連付けられます。(対象の外だしSQLが BehaviorQuery である必要あり)

e.g. DomainEntity 型のオプションを指定 @OutsideSql
-- #df:entity#
-- +domain+
select MEMBER_ID, MEMBER_NAME, BIRTHDATE
  from MEMBER

フリースタイル形式では単に第三引数に DomainEntity 型を指定すれば良いだけなので、これは TypedParameterBean に対してのみ有効のオプションです。 (ちなみに、フリースタイル形式は、1.1より outside().traditionalStyle() という呼び出しに変わっています)

他の自動生成Entityは指定できない!?

例えば、sea.sql で自動生成した CustomizeEntity を、land.sql で再利用しようと思ってもできません。 もし land.sql で sea.sql の Entity 名を指定しても重複エラーとなります。

それができてしまうと、land.sql の方では結局自動生成の恩恵を受けることができず、SQLとEntityの "ズレ" を検知することができません。land.sql の select 句でスペルミスなどがあってもコンパイルで検知されません。

では、もし二つのSQLで select 句が全く同じな場合、どうすれば良いのでしょう?

sea.sql, land.sql ではそれぞれ普通に自動生成し、select 句の項目に合わせた getter メソッドを列挙したインターフェースを(手動)作成し、それぞれの CustomizeEntity の Ex クラスにて implements するやり方をオススメしています。

実際に、検索されたデータを取り扱うロジックでは、インターフェースで受け取るようにします。 インターフェースの手動作成の分少々面倒ですが、そのまま自動生成の恩恵を受けることができ、sea と land のズレを防ぎます。 (どうしても面倒なのであれば、フリースタイル形式で)

プログラム型の強制指定

CustomizeEntityのプロパティの型(プログラム型)は、メタデータより取得したDB上のデータ型情報を元にマッピングされます。 通常ほとんどの場合はこれで問題はありませんが、JDBCドライバ次第でほんの稀に予期せぬ型にマッピングされることがあります。 そのような場合に、型を強制的に指定することができます。SQLの行コメントで ##プログラム型 カラム名## という形で記述します。するとのそのカラムにマッピング型が指定された型になります。

e.g. PURCHASE_PERCENTをBigDecimalに強制的にマッピング @OutsideSql
-- #df:entity#
-- ##BigDecimal PURCHASE_PERCENT##
select sum(PURCHASE_PERCENT) as PURCHASE_PERCENT
  from ...

検索カラムのコメント

※こちらの機能ですが、documentMap.dfprop の isEntityJavaDocDbCommentValid が true に設定されていることが前提となります。(ぜひ、true にしてもらいたいプロパティです)

検索カラム(select句に列挙するカラム)にコメントを付与して、CustomizeEntityのJavaDocコメントに追記することができます。 @since 0.9.9.1A

もともとDBFluteがその検索カラムがどのテーブルのどのカラムなのかを探してJavaDocコメントにしますが、 それができるかはJDBCドライバの実装に依存しますし、副問い合わせによる導出カラムなどではメタデータは取得できない可能性ありますし、 そもそも外だしSQLならではの補足や説明が書きたいでしょう。

検索カラムのコメントの定義の仕方

select句のカラム定義のすぐ後ろに行コメントマーク "--" に続いて "//" と入れた後にコメントを書くと、それがJavaDocコメントに付与されます。 JavaDoc上では、もともとのメタデータによるコメントに続いて "改行 + //" が付与されて、その後にコメントが追記されます。

e.g. 検索カラムにコメントを付与する @OutsideSql
-- #df:entity#

select MEMBER_ID -- // 検索結果のユニーク性を表現する項目
     , MEMBER_NAME -- // スラスラ(//)ってなんか Java っぽい
     , (select max(PURCHASE_PRICE)
          from 
         where ...
       ) as MAX_PURCHASE_PRICE -- // 最大購入価格: 未払いは含めない
  from ...

このようにしておくことで、外だしSQL上でわかりやすいだけでなく、 プログラム上で実装しながら、検索カラムの特性が補完時に表示されるJavaDocコメントで確認できます。

ParameterBeanのプロパティ宣言でも同じようなことができます。合わせて利用すると良いでしょう。

検索カラムのコメントの細かい仕様

JavaDocには追記したくないけど、外だしSQL上ではコメントとして書きたいというような内容のものがある場合は、 行コメントマーク "--" と "//" の間にそのコメントを書くことができます。つまり、"--" と "//" は同じ行であれば離して書くことができます。

e.g. JavaDocに追記しないコメントも書く @OutsideSql
-- #df:entity#

select MEMBER_ID -- これはJavaDocに追記しない // これはJavaDocへ
...

内部的には文字列パースによって実現しているため、書き方によってはJavaDocに追記されないことがあります。 "as" や "." や "," などがポイントとなっていて、基本的には "as" が付いていれば正常に抽出される可能性が高いです。 以下のような基本的な書き方がされていればほとんどの場合において問題はありません。

  • ... as [カラム名]
  • , [テーブル別名].[カラム名]
  • [テーブル別名].[カラム名],
  • , [カラム名]
  • [カラム名],
  • select [カラム名]

一つの行に二つの検索カラムを定義していたり、カラム定義と行コメントの間に何かしらの記述があったりした場合は、 正常にパースすることができない可能性があります。また、ダブルクォーテーションなどで囲ったカラム名はサポート対象外です。

CustomizeEntityの構造

CustomizeEntityクラスの構造は、基本的に DomainEntity と同じです(内部的にも同じテンプレートが利用されています)。なので、Entity インターフェースを実装しており、DBMeta (SQLのメタデータ)にもアクセスできます。また ジェネレーションギャップになっています。

共通カラム(CommonColumn)の認識

SQLの構造 (select句) として(全ての)共通カラムを含んでいる場合、自動的に共通カラムを持ったCustomizeEntity となります。つまり、EntityDefinedCommonColumn インターフェースを実装します。

区分値メソッドの生成

DBFluteプロパティの設定にて区分値と関連付けられているカラムの条件に一致するカラム をSQLの構造 (select句) として含んでいる場合、自動的に区分値メソッドを持ったCustomizeEntityとなります。 Sql2Entityのメタデータとしてカラムの参照先カラムが判明している場合は、 その参照先カラムの情報をもとに一致するかどうか判定されます(ただし、これはJDBCドライバ次第)。

e.g. MEMBER_STATUS_CODEという名前が区分値として関連付けられている場合 @OutsideSql
-- #df:entity#
select MEMBER_ID
     , MEMBER_STATUS_CODE -- treated as classification
     , MEMBER_STATUS_CODE as FOO -- treated as classification (depends on JDBC)
     , 0 as MEMBER_STATUS_CODE -- treated as classification
  from MEMBER
e.g. _FLGで終わるカラムが区分値として関連付けられている場合 @OutsideSql
-- #df:entity#
select MEMBER_ID
     , 0 as DELETE_FLG -- treated as classification
     , 0 as DELETE_NOFLG -- NOT treated as classification
  from MEMBER

JDBCドライバがどうしてもカラムのメタデータを提供できないケースがあります。 例えば、インラインビューでunionを使っているなど、ケースはDMBSによってさまざまです。

そこで、強制的に区分値の関連付けヒントを指定することができます。 検索カラムのコメントの中で cls([区分値名]) と指定すると、指定された区分値が関連付きます。@since 1.0.5M

e.g. _FLGで終わるカラムが区分値として関連付けられている場合 @OutsideSql
-- #df:entity#
select MEMBER_ID
     , MEMBER_STATUS_CODE -- // コメントの中であればどこでもOK cls(MemberStatus)
  from MEMBER