
Welcome to Doma¶
Doma は Java のDBアクセスフレームワークです。
Doma のバージョンには 1 と 2 がありますが、 このドキュメントは バージョン 2 を対象としています。
Doma 2 には以下の特徴があります。
- 注釈処理を使用して コンパイル時 にコードの生成やコードの検証を行う
- データベース上のカラムの値を振る舞いを持った Java オブジェクトにマッピングできる
- 2-way SQL と呼ばれる SQL テンプレートを利用できる
- Java 8 の java.time.LocalDate や java.util.Optional や java.util.stream.Stream を利用できる
- JRE 以外のライブラリへの依存が一切ない
このドキュメントは複数のセクションから成ります。
User Documentation¶
Getting Started¶
目次
概要¶
開発環境のセットアップ方法と基本的なデータベースアクセスの実行方法を紹介します。
ノート
このドキュメントでは、IDE として Eclipse を用いますが、 Eclipse の代わりに IntelliJ IDEA を利用して開発することも可能です。 IntelliJ IDEA を利用する場合は、 IntelliJ Doma support plugin の併用をお奨めします。
JDK のインストール¶
JDK 8 をインストールしてください。
Eclipse のインストール¶
Eclipse Standard 4.4 をインストールしてください。
ノート
Eclipse IDE for Java EE Developers など他のパッケージでも動作しますが このドキュメントでは Eclipse Standard を対象とします。
Eclipse プラグイン Doma Tools のインストール¶
Doma Tools は Java ファイルと SQL ファイルの相互遷移を可能にするプラグインです。 Doma の利用に必須ではありませんが、このプラグインを使用すると生産性が高まります。
Eclipse メニューバーから Help > Install New Software... と進み、 ‘Work With’ のテキストボックスに次のURLを入力してください。
http://dl.bintray.com/domaframework/eclipse/
以下の図のようにインストール可能なプラグインの候補が表示されるので Doma Tools の最新バージョンにチェックをつけてダイアログを進め インスートルを完了してください。

ファイルの関連づけ¶
Doma Tools は、 SQL ファイルの更新をフックして注釈処理を実行します。 そのためには、 SQL ファイルを Eclipse 内で開く必要があります。
メニューバーから Eclipse > 環境設定... もしくは Window > Preference と選択し、設定画面を開いてください。
以下の図が示すように .sql の拡張子をもつファイルを Text Editor に関連づけてください。

同様に .script の拡張子をもつファイルを Text Editor に関連づけてください。

ノート
Eclipse IDE for Java EE Developers を利用する場合は、 デフォルトでSQLファイルが専用のエディタに関連づけられているため この手順をスキップできます。
ノート
SQL は RDBMS 固有のツール(Oracle SQL Developer や pgAdmin)で作成し、 完成したものを Eclipse のエディターにコピーするといった 開発スタイルをお奨めします。
雛形プロジェクトのインポート¶
GitHub から simple-boilerplate を clone してください。
$ git clone https://github.com/domaframework/simple-boilerplate.git
clone されたディレクトリに移動します。
$ cd simple-boilerplate
次のコマンドで Eclipse 用の設定ファイルを生成します。
$ ./gradlew eclipse
ノート
Windows 環境では ./gradlew eclipse とする代わりに gradlew eclipse としてください。
ノート
環境変数 JAVA_HOME に JDK 8 をインストールしたディレクトリを設定しておいてください。 gradlew の実行に必要です。
Eclipse のメニューからFile > Import... を実行し ‘Existing Projects into Workspace’ を選んで simple-boilerplate をインポートします。

インポートが成功したことを確認するためにプロジェクトを選択して JUnit を実行してください。 テストが1件成功すれば正常にインポートできています。
雛形プロジェクトの構成¶
プロジェクトのソースコードの構成は次のようになっています。
─ src
├── main
│ ├── java
│ │ └── boilerplate
│ │ ├── AppConfig.java
│ │ ├── dao
│ │ │ ├── AppDao.java
│ │ │ └── EmployeeDao.java
│ │ └── entity
│ │ └── Employee.java
│ └── resources
│ └── META-INF
│ └── boilerplate
│ └── dao
│ ├── AppDao
│ │ ├── create.script
│ │ └── drop.script
│ └── EmployeeDao
│ ├── selectAll.sql
│ └── selectById.sql
└── test
├── java
│ └── boilerplate
│ ├── DbResource.java
│ └── dao
│ └── EmployeeDaoTest.java
└── resources
主要なものについて説明します。
- AppConfig.java
- Doma を実行するために必要な 設定 です。
- AppDao.java
- このアプリケーションで利用するデータベースのスキーマを実行時に作成/破棄するユーティリティです。 実環境では不要になります。 スキーマの作成と破棄には META-INF/boilerplate/dao/AppDao/ 以下のスクリプトファイルを使用します。
- Employee.java
- データベースの EMPLOYEE テーブルに対応する エンティティクラス です。
- EmployeeDao.java
- Employee クラスの取得や更新などを行う Daoインタフェース です。 META-INF/boilerplate/dao/EmployeeDao/ 以下の SQLファイル を使用します。
- EmployeeDaoTest.java
- EmployeeDao を使ったテストです。 このファイルにテストケースを追加しながら Doma の学習ができます。 テストメソッドごとにデータベーススキーマの作成と破棄を行っているため データの更新によって他のテストが影響を受けることはありません。
Java と SQL の相互遷移¶
EmployeeDao.java では次のように定義されています。
@Dao(config = AppConfig.class)
public interface EmployeeDao {
@Select
List<Employee> selectAll();
@Select
Employee selectById(Integer id);
@Insert
int insert(Employee employee);
@Update
int update(Employee employee);
@Delete
int delete(Employee employee);
}
Eclipse のエディタ上で selectById メソッドにカーソルを合わせ右クリックなどで コンテキストメニューを表示させてください。 メニューの中から Doma > Jump to SQL を選択すると META-INF/boilerplate/dao/EmployeeDao/selectById.sql ファイルへ遷移できます。
次に、META-INF/boilerplate/dao/EmployeeDao/selectById.sql ファイルの任意の場所に カーソルを置き、コンテキストメニューを表示させてください。 メニューの中から Doma > Jump to Java を選択すると EmployeeDao.java ファイルへ戻ってこられます。
SQL ファイル¶
META-INF/boilerplate/dao/EmployeeDao/selectById.sql ファイルを開いてください。 このファイルには次のように記述されています。
select
/*%expand*/*
from
employee
where
id = /* id */0
/*%expand*/ は Java メソッドでマッッピングされた エンティティクラスの定義を参照してカラムリストを展開することを示しています。
/* id */ は Java メソッドのパラメータの値がこの SQL へバインドされることを 示しています。
後ろにある 0 はテスト用のデータです。 このテストデータを含めることで、 SQL をツールで実行して構文上の 誤りがないことを容易に確認できます。 テスト用のデータは Java プログラム実行時には使われません。
詳細については、 SQL を参照してください。
検索¶
検索 処理を実行するには、 @Select が注釈された Dao メソッドを呼び出します。
検索処理の追加¶
ある年齢より小さい従業員を検索する処理を追加する手順を示します。
EmployeeDao に次のコードを追加してください。
@Select
List<Employee> selectByAge(Integer age);
このとき、注釈処理により次のエラーメッセージが Eclilpse 上に表示されます。
[DOMA4019] ファイル[META-INF/boilerplate/dao/EmployeeDao/selectByAge.sql]が
クラスパスから見つかりませんでした。
Eclipse のエディタ上で selectByAge メソッドにカーソルを合わせ右クリックなどで コンテキストメニューを表示させ、メニューの中から Doma > Jump to SQL を選択してください。
SQL ファイルの新規作成を行うためのダイアログが次のように表示されます。

‘Finish’ を押してファイルを作成してください。
ファイル作成後、ファイルを空のまま保管して EmployeeDao に戻ると エラーメッセージの内容が変わります。
[DOMA4020] SQLファイル[META-INF/boilerplate/dao/EmployeeDao/selectByAge.sql]が空です。
selectByAge.sql ファイルに戻って次の SQL を記述してください。
select
/*%expand*/*
from
employee
where
age < /* age */0
これでエラーが解消されます。
検索処理の実行¶
上記で作成した検索処理を実際に実行します。
EmployeeDaoTest に次のコードを追加してください。
@Test
public void testSelectByAge() {
TransactionManager tm = AppConfig.singleton().getTransactionManager();
tm.required(() -> {
List<Employee> employees = dao.selectByAge(35);
assertEquals(2, employees.size());
});
}
JUnit を実行し、このコードが動作することを確認してください。
このとき発行される検索のための SQL は次のものです。
select
age, id, name, version
from
employee
where
age < 35
挿入¶
挿入 処理を実行するには、 @Insert が注釈された Dao メソッドを呼び出します。
挿入処理の実行¶
EmployeeDao に次のコードが存在することを確認してください。
@Insert
int insert(Employee employee);
このコードを利用して挿入処理を実行します。
EmployeeDaoTest に次のコードを追加してください。
@Test
public void testInsert() {
TransactionManager tm = AppConfig.singleton().getTransactionManager();
Employee employee = new Employee();
// 最初のトランザクション
// 挿入を実行している
tm.required(() -> {
employee.name = "HOGE";
employee.age = 20;
dao.insert(employee);
assertNotNull(employee.id);
});
// 2番目のトランザクション
// 挿入が成功していることを確認している
tm.required(() -> {
Employee employee2 = dao.selectById(employee.id);
assertEquals("HOGE", employee2.name);
assertEquals(Integer.valueOf(20), employee2.age);
assertEquals(Integer.valueOf(1), employee2.version);
});
}
JUnit を実行し、このコードが動作することを確認してください。
このとき発行される挿入のための SQL は次のものです。
insert into Employee (age, id, name, version) values (20, 100, 'HOGE', 1)
識別子とバージョン番号が自動で設定されています。
更新¶
更新 処理を実行するには、 @Update が注釈された Dao メソッドを呼び出します。
更新処理の実行¶
EmployeeDao に次のコードが存在することを確認してください。
@Update
int update(Employee employee);
このコードを利用して更新処理を実行します。
EmployeeDaoTest に次のコードを追加してください。
@Test
public void testUpdate() {
TransactionManager tm = AppConfig.singleton().getTransactionManager();
// 最初のトランザクション
// 検索して age フィールドを更新している
tm.required(() -> {
Employee employee = dao.selectById(1);
assertEquals("ALLEN", employee.name);
assertEquals(Integer.valueOf(30), employee.age);
assertEquals(Integer.valueOf(0), employee.version);
employee.age = 50;
dao.update(employee);
assertEquals(Integer.valueOf(1), employee.version);
});
// 2番目のトランザクション
// 更新が成功していることを確認している
tm.required(() -> {
Employee employee = dao.selectById(1);
assertEquals("ALLEN", employee.name);
assertEquals(Integer.valueOf(50), employee.age);
assertEquals(Integer.valueOf(1), employee.version);
});
}
JUnit を実行し、このコードが動作することを確認してください。
このとき発行される更新のための SQL は次のものです。
update Employee set age = 50, name = 'ALLEN', version = 0 + 1 where id = 1 and version = 0
楽観的排他制御のためのバージョン番号が自動でインクリメントされています。
削除¶
削除 処理を実行するには、 @Delete が注釈された Dao メソッドを呼び出します。
削除処理の実行¶
EmployeeDao に次のコードが存在することを確認してください。
@Delete
int delete(Employee employee);
このコードを利用して削除処理を実行します。
EmployeeDaoTest に次のコードを追加してください。
@Test
public void testDelete() {
TransactionManager tm = AppConfig.singleton().getTransactionManager();
// 最初のトランザクション
// 削除を実行している
tm.required(() -> {
Employee employee = dao.selectById(1);
dao.delete(employee);
});
// 2番目のトランザクション
// 削除が成功していることを確認している
tm.required(() -> {
Employee employee = dao.selectById(1);
assertNull(employee);
});
}
JUnit を実行し、このコードが動作することを確認してください。
このとき発行される削除のための SQL は次のものです。
delete from Employee where id = 1 and version = 0
識別子に加えバージョン番号も検索条件に指定されます。
設定¶
目次
Domaに対する設定は、 Confing インタフェースの実装クラスで表現します。
設定項目¶
設定必須と明示していない項目についてはデフォルトの値が使用されます。
データソース¶
DataSource を getDataSource メソッドで返してください。 ローカルトランザクションを利用する場合は、 LocalTransactionDataSource を返してください。
ノート
この項目は設定必須です。
データソースの名前¶
データソース名をあらわす String を getDataSourceName メソッドで返してください。 データソース名は、複数のデータソースを利用する環境で重要です。 データソース名はデータソースごとに自動生成される識別子を区別するために使用されます。 複数データソースを利用する場合は、それぞれ異なる名前を返すようにしてください。
デフォルトの実装では、 Config の実装クラスの完全修飾名が使用されます。
データベースの方言¶
Dialect を getDialect メソッドで返してください。 Dialect はRDBMSの方言を表すインタフェースです。 Dialect には次のものがあります。
データベース | Dialect | 説明 |
---|---|---|
DB2 | Db2Dialect | |
H2 Database Engine 1.2.126 | H212126Dialect | H2 Database Engine 1.2.126で稼動 |
H2 Database | H2Dialect | H2 Database Engine 1.3.171以降に対応 |
HSQLDB | HsqldbDialect | |
Microsoft SQL Server 2008 | Mssql2008Dialect | Microsoft SQL Server 2008に対応 |
Microsoft SQL Server | MssqlDialect | Microsoft SQL Server 2012以降に対応 |
MySQL | MySqlDialect | |
Oracle Database | OracleDialect | |
PostgreSQL | PostgresDialect | |
SQLite | SqliteDialect |
ノート
この項目は設定必須です。
ログ出力ライブラリへのアダプタ¶
JdbcLogger を getJdbcLogger メソッドで返してください。 JdbcLogger はデータベースアクセスに関するログを扱うインタフェースです。 実装クラスには次のものがあります。
- org.seasar.doma.jdbc.UtilLoggingJdbcLogger
UtilLoggingJdbcLogger は java.util.logging のロガーを使用する実装で、 デフォルトで使用されます。
SQLファイルのリポジトリ¶
SqlFileRepository を getSqlFileRepository メソッドで返してください。 SqlFileRepository は SQL ファイルのリポジトリを扱うインタフェースです。 実装クラスには次のものがあります。
- org.seasar.doma.jdbc.GreedyCacheSqlFileRepository
- org.seasar.doma.jdbc.NoCacheSqlFileRepository
GreedyCacheSqlFileRepository は、読み込んだSQLファイルの内容をパースし、 その結果をメモリが許す限り最大限にキャッシュします。
NoCacheSqlFileRepository は、一切キャッシュを行いません。 毎回、SQLファイルからSQLを読み取りパースします。
メモリの利用に厳しい制限がある環境や、扱うSQLファイルが膨大にある環境では、 適切なキャッシュアルゴリズムをもった実装クラスを作成し使用してください。
デフォルトでは GreedyCacheSqlFileRepository が使用されます。
REQUIRES_NEW 属性のトランザクションとの連動¶
RequiresNewController を getRequiresNewController メソッドで返してください。 RequiresNewController は REQUIRES_NEW の属性をもつトランザクションを 制御するインタフェースです。
このインタフェースは、 @TableGenerator で、識別子を自動生成する際にしか使われません。 @TableGenerator を利用しない場合は、この設定項目を考慮する必要はありません。 また、 @TableGenerator を利用する場合であっても、 識別子を採番するための更新ロックが問題にならない程度のトランザクション数であれば、 設定する必要ありません。
デフォルトの実装は何の処理もしません。
クラスのロード方法¶
ClassHelper を getClassHelper メソッドで返してください。 ClassHelper はクラスのロードに関してアプリケーションサーバや フレームワークの差異を抽象化するインタフェースです。
デフォルトの実装は java.lang.Class.forName(name) を用いてクラスをロードします。
例外メッセージに含めるSQLの種別¶
例外メッセージに含めるSQLのタイプをあらわす SqlLogType を getExceptionSqlLogType メソッドで返してください。 この値は、Doma がスローする例外にどのような形式のSQLを含めるかを決定します。
デフォルトの実装では、フォーマットされた SQL を含めます。
未知のカラムのハンドラ¶
UnknownColumnHandler を getUnknownColumnHandler メソッドで返してください。 UnknownColumnHandler は 検索 の結果を エンティティクラス にマッピングする際、 エンティティクラスにとって未知のカラムが存在する場合に実行されます。
デフォルトでは、 UnknownColumnException がスローされます。
テーブルやカラムにおけるネーミング規約の制御¶
Naming を getNaming メソッドで返してください。
Naming は、 @Entity の name 要素に指定された(もしくは指定されない) NamingType をどのように適用するかについて制御するインタフェースです。 このインタフェースを使うことで、個別のエンティティクラスに NamingType を指定しなくても エンティティのクラス名とプロパティ名からデータベースのテーブル名とカラム名を解決できます。
Naming が使用される条件は以下の通りです。
- @Table や @Column の name 要素に値が指定されていない。
一般的なユースケースを実現するための実装は、 Naming の static なメンバに定義されています。
デフォルトでは、 Naming.NONE が使用されます。 この実装は、エンティティクラスに指定された NamingType を使い、 指定がない場合は何の規約も適用しません。
例えば、指定がない場合にスネークケースの大文字を適用したいというケースでは、 Naming.SNAKE_UPPER_CASE を使用できます。
マップのキーのネーミング規約の制御¶
MapKeyNaming を getMapKeyNaming メソッドで返してください。 MapKeyNaming は検索結果を java.util.Map<String, Object> にマッピングする場合に実行されます。
デフォルトでは、 @Select などの mapKeyNaming 要素に指定された規約を適用します。
ローカルトランザクションマネージャー¶
LocalTransactionManager を getTransactionManager メソッドで返してください。 getTransactionManager メソッドは、デフォルトで UnsupportedOperationException をスローします。
ノート
この項目は設定必須ではありませんが、 org.seasar.doma.jdbc.tx.TransactionManager のインタフェースでトランザクションを利用したい場合は設定してください。 設定方法については トランザクション を参照してください。
SQLの識別子の追記¶
Commenter を getCommenter メソッドで返してください。 Commenter はSQLの識別子(SQLの発行箇所等を特定するための文字列)をSQLコメントとして追記するためのインタフェースです。
実装クラスには次のものがあります。
- org.seasar.doma.jdbc.CallerCommenter
CallerCommenter は、SQLの呼び出し元のクラス名とメソッド名を識別子として使用します。
デフォルトの実装では、 識別子を追記しません。
Command の実装¶
CommandImplementors を getCommandImplementors メソッドで返してください。 CommandImplementors を実装すると クエリ の実行をカスタマイズできます。
たとえば、 JDBC の API を直接呼び出すことができます。
Query の実装¶
QueryImplementors を getQueryImplementors メソッドで返してください。 QueryImplementors を実装すると クエリ の内容をカスタマイズできます。
たとえば、自動生成される SQL の一部を書き換え可能です。
エンティティリスナーの取得¶
EntityListenerProvider を getEntityListenerProvider メソッドで返して下さい。
EntityListenerProvider の get メソッドは EntityListener 実装クラスの Class と EntityListener 実装クラスのインスタンスを返す Supplier を引数に取り EntityListener のインスタンスを返します。 デフォルトの実装では Supplier.get メソッドを実行して得たインスタンスを返します。
EntityListener 実装クラスのインスタンスをDIコンテナから取得したいなど、 インスタンス取得方法をカスタマイズする場合は EntityListenerProvider を実装したクラスを作成し、 getEntityListenerProvider メソッドでそのインスタンスを返すよう設定してください。
JDBC ドライバのロード¶
クラスパスが通っていれば JDBC ドライバは サービスプロバイダメカニズム により自動でロードされます。
警告
実行環境によっては、 JDBC ドライバが自動でロードされないことがあります。 たとえば Tomcat 上では WEB-INF/lib に配置された JDBC ドライバは自動でロードされません 。 そのような環境においては、その環境に応じた適切は方法を採ってください。 たとえば Tomcat 上で動作させるためのには、上記のリンク先の指示に従って ServletContextListener を利用したロードとアンロードを行ってください。
定義と利用例¶
シンプル¶
シンプルな定義は次の場合に適しています。
- DIコンテナで管理しない
- ローカルトランザクションを使用する
実装例です。
@SingletonConfig
public class AppConfig implements Config {
private static final AppConfig CONFIG = new AppConfig();
private final Dialect dialect;
private final LocalTransactionDataSource dataSource;
private final TransactionManager transactionManager;
private AppConfig() {
dialect = new H2Dialect();
dataSource = new LocalTransactionDataSource(
"jdbc:h2:mem:tutorial;DB_CLOSE_DELAY=-1", "sa", null);
transactionManager = new LocalTransactionManager(
dataSource.getLocalTransaction(getJdbcLogger()));
}
@Override
public Dialect getDialect() {
return dialect;
}
@Override
public DataSource getDataSource() {
return dataSource;
}
@Override
public TransactionManager getTransactionManager() {
return transactionManager;
}
public static AppConfig singleton() {
return CONFIG;
}
}
ノート
クラスに @SingletonConfig を注釈するのを忘れないようにしてください。
利用例です。 定義した設定クラスは、@Daoに指定します。
@Dao(config = AppConfig.class)
public interface EmployeeDao {
@Select
Employee selectById(Integer id);
}
アドバンスド¶
アドバンスドな定義は次の場合に適しています。
- DIコンテナでシングルトンとして管理する
- DIコンテナやアプリケーションサーバーが提供するトランザクション管理機能を使う
実装例です。 dialect と dataSource はDIコンテナによってインジェクションされることを想定しています。
public class AppConfig implements Config {
private Dialect dialect;
private DataSource dataSource;
@Override
public Dialect getDialect() {
return dialect;
}
public void setDialect(Dialect dialect) {
this.dialect = dialect;
}
@Override
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
利用例です。 定義した設定クラスのインスタンスがDIコンテナによってインジェクトされるようにします。
@Dao
@AnnotateWith(annotations = {
@Annotation(target = AnnotationTarget.CONSTRUCTOR, type = javax.inject.Inject.class),
@Annotation(target = AnnotationTarget.CONSTRUCTOR_PARAMETER, type = javax.inject.Named.class, elements = "\"config\"") })
public interface EmployeeDao {
@Select
Employee selectById(Integer id);
}
上記の例では @AnnotateWith の記述をDaoごとに繰り返し記述する必要があります。 繰り返しを避けたい場合は、任意のアノテーションに一度だけ @AnnotateWith を記述し、 Daoにはそのアノテーションを注釈してください。
@AnnotateWith(annotations = {
@Annotation(target = AnnotationTarget.CONSTRUCTOR, type = javax.inject.Inject.class),
@Annotation(target = AnnotationTarget.CONSTRUCTOR_PARAMETER, type = javax.inject.Named.class, elements = "\"config\"") })
public @interface InjectConfig {
}
@Dao
@InjectConfig
public interface EmployeeDao {
@Select
Employee selectById(Integer id);
}
基本型¶
Domaでは、データベースのカラムにマッピング可能なJavaの型を基本型と呼びます。
種類¶
基本型には以下の種類があります。
- プリミティブ型とそのラッパー型(ただし char と java.lang.Character は除く)
- 列挙型
- byte[]
- java.lang.String
- java.lang.Object
- java.math.BigDecimal
- java.math.BigInteger
- java.time.LocalDate
- java.time.LocalTime
- java.time.LocalDateTime
- java.sql.Date
- java.sql.Time
- java.sql.Timestamp
- java.sql.Array
- java.sql.Blob
- java.sql.Clob
- java.sql.SQLXML
- java.util.Date
日付/時刻型¶
日付と時刻の型の違いについて説明します。
- java.time.LocalDate
- SQL標準のDATE型 (日付のみ)を表します。
- java.time.LocalTime
- SQL標準のTIME型 (時刻のみ)を表します。
- java.time.LocalDateTime
- SQL標準のTIMESTAMP型 (日付と時刻)を表します。RDBMSがサポートしている場合ナノ秒を保持します。
- java.sql.Date
- SQL標準のDATE型 (日付のみ)を表します。
- java.sql.Time
- SQL標準のTIME型 (時刻のみ)を表します。
- java.sql.Timestamp
- SQL標準のTIMESTAMP型 (日付と時刻)を表します。RDBMSがサポートしている場合ナノ秒を保持します。
- java.util.Date
- SQL標準のTIMESTAMP型 (日付と時刻)を表します。ナノ秒を保持しません。
利用例¶
エンティティクラス¶
@Entity
public class Employee {
@Id
Integer employeeId;
Optional<String> employeeName;
@Version
Long versionNo;
...
}
ドメインクラス¶
Domain (ドメイン)クラスの定義方法を示します。
Doma では、テーブルのカラムの値を ドメイン と呼ばれる Java オブジェクトで扱えます。 ドメインとは値のとり得る範囲、つまり定義域のことです。
ドメインクラスを利用することで、データベース上のカラムの型が同じあっても アプリケーション上意味が異なるものを別のJavaの型で表現できます。 これにより意味を明確にしプログラミングミスを事前に防ぎやすくなります。 また、ドメインクラスに振る舞いを持たせることでよりわかりやすいプログラミングが可能です。
ドメインクラスの作成と利用は任意です。 ドメインクラスを利用しなくても Integer や String など基本型のみでデータアクセスは可能です。
ドメインは、定義の仕方により内部ドメインと外部ドメインに分けられます。
内部ドメイン¶
ドメインとして扱いたい対象のクラスのソースコードに直接定義を記述します。
内部ドメインを定義するには、クラスに @Domain を注釈します。
@Domain の valueType 要素には 基本型 を指定します。
コンストラクタで生成する方法¶
@Domain の factoryMethod 要素のデフォルトの値は new であり、 非privateなコンストラクタでインスタンスを生成することを示します。 そのため、コンストラクタで生成する場合は factoryMethod 要素を省略できます。 次の例では、 public なコンストラクタを持つドメインクラスを作成しています。 このクラスは電話番号を表しています。
@Domain(valueType = String.class)
public class PhoneNumber {
private final String value;
public PhoneNumber(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public String getAreaCode() {
// ドメインに固有の振る舞いを記述できる。
...
}
}
ファクトリメソッドで生成する方法¶
コンストラクタをprivateにしファクトリメソッドを使ってインスタンスを生成したい場合は、 staticな非privateなメソッドを定義し @Domain の factoryMethod 要素にそのメソッドの名前を指定します。 次の例では、publicなファクトリメソッドをもつドメインクラスを作成しています。 このクラスは電話番号を表しています。
@Domain(valueType = String.class, factoryMethod = "of")
public class PhoneNumber {
private final String value;
private PhoneNumber(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public String getAreaCode() {
// ドメインに固有の振る舞いを記述できる。
...
}
public static PhoneNumber of(String value) {
return new PhoneNumber(value);
}
}
次の例では、 public なファクトリメソッドをもつ列挙型をドメインクラスとして作成しています。 この列挙型は仕事の種別を表しています。
@Domain(valueType = String.class, factoryMethod = "of")
public enum JobType {
SALESMAN("10"),
MANAGER("20"),
ANALYST("30"),
PRESIDENT("40"),
CLERK("50");
private final String value;
private JobType(String value) {
this.value = value;
}
public static JobType of(String value) {
for (JobType jobType : JobType.values()) {
if (jobType.value.equals(value)) {
return jobType;
}
}
throw new IllegalArgumentException(value);
}
public String getValue() {
return value;
}
}
型パラメータを利用する方法¶
ドメインクラスには任意の数の型パラメータを宣言できます。 次の例では、1つの型パラメータを持ち、さらに public なコンストラクタを持つ ドメインクラスを作成しています。 このクラスは識別子を表しています。
@Domain(valueType = int.class)
public class Identity<T> {
private final int value;
public Identity(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
型パラメータを持ったドメインクラスはファクトリメソッドで生成することも可能です。 この場合、ファクトリメソッドにはクラスの型変数宣言と同等の宣言が必要です。
@Domain(valueType = int.class, factoryMethod = "of")
public class Identity<T> {
private final int value;
private Identity(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static <T> Identity<T> of(int value) {
return new Identity<T>(value);
}
}
外部ドメイン¶
ドメインとして扱いたい対象のクラスとは別のクラスに定義を記述します。
外部ドメインは、ソースコードに手を加えられない、 Doma へ依存させたくない、 といった理由がある場合に有効です。 外部ドメインを定義するには、 DomainConverter の実装クラスに @ExternalDomain を注釈して示します。
例えば、次のような PhoneNumber というクラスがありソースコードに手を加えられないとします。
public class PhoneNumber {
private final String value;
public PhoneNumber(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public String getAreaCode() {
...
}
}
上記の PhoneNumber をドメインクラスとして扱うには、次のようなクラスを作成します。
@ExternalDomain
public class PhoneNumberConverter implements DomainConverter<PhoneNumber, String> {
public String fromDomainToValue(PhoneNumber domain) {
return domain.getValue();
}
public PhoneNumber fromValueToDomain(String value) {
if (value == null) {
return null;
}
return new PhoneNumber(value);
}
}
これで外部ドメイン定義は完成ですが、これだけではまだ利用できません。 外部ドメイン定義を @DomainConverters へ登録します。 @DomainConverters には複数の外部ドメイン定義を登録可能です。
@DomainConverters({ PhoneNumberConverter.class })
public class DomainConvertersProvider {
}
そして最後に、 @DomainConverters が注釈されたクラスの完全修飾名を 注釈処理 のオプションに指定します。 オプションのkeyは、 doma.domain.converters です。
型パラメータを利用する方法¶
任意の数の型パラメータを持ったクラスを扱えます。 次の例のような1つの型パラメータを持つクラスがあるとします。 このクラスは識別子を表しています。
public class Identity<T> {
private final int value;
public Identity(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
上記の Identity をドメインクラスとして扱うには、次のようなクラスを作成します。 Identity の型パラメータにはワイルドカード ? を指定しなければいけません。
@ExternalDomain
public class IdentityConverter implements DomainConverter<Identity<?>, String> {
public String fromDomainToValue(Identity<?> domain) {
return domain.getValue();
}
@SuppressWarnings("rawtypes")
public Identity<?> fromValueToDomain(String value) {
if (value == null) {
return null;
}
return new Identity(value);
}
}
その他の設定方法については、型パラメータを使用しない場合と同様です。
利用例¶
ドメインクラスが型パラメータを持つ場合、型パラメータには具体的な型が必要です。 ワイルドカード ? や型変数の指定はサポートされていません。
@Entity
public class Employee {
@Id
Identity<Employee> employeeId;
String employeeName;
PhoneNumber phoneNumber;
JobType jobType;
@Version
Integer versionNo();
...
}
@Dao(config = AppConfig.class)
public interface EmployeeDao {
@Select
Employee selectById(Identity<Employee> employeeId);
@Select
Employee selectByPhoneNumber(PhoneNumber phoneNumber);
@Select
List<PhoneNumber> selectAllPhoneNumber();
@Select
Employee selectByJobType(JobType jobType);
@Select
List<JobType> selectAllJobTypes();
}
エンベッダブルクラス¶
Embeddable(エンベッダブル)は、データベースのテーブルやクエリの結果セット複数カラムをグループ化します。
エンベッダブル定義¶
エンベッダブルクラスは @Enbeddable を注釈して示します。 コンストラクタには永続的なフィールドに対応するパラメータが必要です。
@Embeddalbe
public class Address {
final String city;
final String street;
@Column(name = "ZIP_CODE")
final String zip;
public Address(String city, String street, String zip) {
this.city = city;
this.street = street;
this.zip = zip;
}
}
エンベッダブルクラスは エンティティクラス のフィールドとして使用します。
@Entity
public class Employee {
@Id
Integer id;
Address address;
}
テーブルや結果セットとのマッピングにおいて、上記のクラス定義は下記のクラス定義と同等です。
@Entity
public class Employee {
@Id
Integer id;
String city;
String street;
@Column(name = "ZIP_CODE")
String zip;
}
フィールド定義¶
エンベッダブルクラスのフィールドはデフォルトで永続的です。 つまり、テーブルや結果セットのカラムに対応します。 フィールドの型は次のいずれかでなければいけません。
- 基本型
- ドメインクラス
- 基本型 または ドメインクラス のいずれかを要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
@Embeddalbe
public class Address {
...
String street;
}
非永続的なフィールド¶
非永続的なフィールドは、@Transient を注釈して示します。
取得時の状態を管理するフィールド¶
エンベッダブルクラスには取得時の状態を管理するフィールドを定義できません。
エンティティクラス¶
目次
Entity(エンティティ)は、データベースのテーブルやクエリの結果セットに対応します。
エンティティ定義¶
エンティティクラスは @Entity を注釈して示します。
@Entity
public class Employee {
...
}
エンティティリスナー¶
エンティティがデータベースに対し挿入、更新、削除される直前/直後に処理を実行したい場合、 @Entity の listener 要素に EntityListener の実装クラスを指定できます。
@Entity(listener = EmployeeEntityListener.class)
public class Employee {
...
}
listener 要素に何も指定しない場合、エンティティクラスが他のエンティティクラスを継承 しているかどうかで採用する設定が変わります。
- 継承している場合、親エンティティクラスの設定を引き継ぎます
- 継承していない場合、何も行いません( NullEntityListener が使用されます)
ネーミング規約¶
エンティティに対応するテーブル名やプロパティに対応するカラム名を解決するためのネーミング規約 を変更したい場合、 naming 要素に NamingType の列挙型を指定できます。
@Entity(naming = NamingType.SNAKE_UPPER_CASE)
public class EmployeeInfo {
...
}
naming 要素に何も指定しない場合、エンティティクラスが他のエンティティクラスを継承している かどうかで採用する設定が変わります。
- 継承している場合、親エンィティクラスの設定を引き継ぎます
- 継承していない場合、何も行いません( NamingType.NONE が使用されます)
NamingType.SNAKE_UPPER_CASE は、エンティティ名やプロパティ名を スネークケース(アンダースコア区切り)の大文字に変換します。 この例の場合、テーブル名はEMPLOYEE_INFOになります。
naming 要素に何も指定しない場合、デフォルトでは、テーブル名にはエンティティクラスの単純名、 カラム名にはプロパティ名が使用されます。
ネーミング規約は、 @Table や @Colum の name 要素が指定されない場合のみ使用されます。 @Table や @Colum の name 要素が指定された場合は、 name 要素 に指定した値が使用され、ネーミング規約は適用されません。
イミュータブルなエンティティ¶
エンティティをイミュータブルなオブジェクトとして扱いたい場合は @Entity の immutable 要素に true を設定します。
@Entity(immutable = true)
public class Employee {
@Id
final Integer id;
final String name;
@Version
final Integer version;
public Employee(Integer id, String name, Integer version) {
this.id = id;
this.name = name;
this.version = version;
}
}
永続的なフィールドには final 修飾子が必須です。
フィールド定義¶
エンティティクラスのフィールドはデフォルトで永続的です。 つまり、テーブルや結果セットのカラムに対応します。 フィールドの型は次のいずれかでなければいけません。
- 基本型
- ドメインクラス
- エンベッダブルクラス
- 基本型 または ドメインクラス のいずれかを要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
@Entity
public class Employee {
...
Integer employeeId;
}
カラム¶
カラム情報を指定するには、 @Column を使用します。
name 要素でカラム名を指定できます。
@Column(name = "ENAME")
String employeeName;
insertable 要素や updatable 要素で挿入や更新の対象とするかどうかを指定できます。
@Column(insertable = false, updatable = false)
String employeeName;
@Column を使用しない、もしくは @Column の name 要素を使用しない場合、 カラム名は ネーミング規約 により解決されます。
ノート
フィールドの型が エンベッダブルクラス の場合、 @Column は指定できません。
識別子¶
識別子(主キー)であることを指定するには、 @Id を使います。
@Id
Integer id;
複合主キーの場合は @Id を複数指定します。
@Id
Integer id;
@Id
Integer id2;
ノート
フィールドの型が エンベッダブルクラス の場合、 @Id は指定できません。
識別子の自動生成¶
識別子を自動生成するには @GeneratedValue を注釈して示します。 フィールドの型は以下のいずれかでなければいけません。
- java.lang.Number のサブタイプ
- java.lang.Number のサブタイプを値とする ドメインクラス
- 上記のいずれかを要素の型とする java.util.Optional
- OptionalInt
- OptionalLong
- OptionalDouble
- 数値のプリミティブ型
ノート
プリミティブ型を使う場合、自動生成された値が確実に設定されるようにするには初期値に -1 など 0 未満の値を明示してください。
IDENTITYを使った識別子の自動生成¶
データベースのIDENTITY自動生成機能を利用する方法です。 RDBMSによってはサポートされていません。 フィールドに対応するカラムの定義でIDENTITY自動生成を有効にしておく必要があります。
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id;
シーケンスを使った識別子の自動生成¶
データベースのシーケンスを利用する方法です。 RDBMSによってはサポートされていません。
@SequenceGenerator では、シーケンスの名前、割り当てサイズ、初期値等を設定できます。 データベースにあらかじめシーケンスを定義しておく必要がありますが、 その定義は @SequenceGenerator の定義とあわせておく必要があります。
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@SequenceGenerator(sequence = "EMPLOYEE_SEQ")
Integer id;
テーブルを使った識別子の自動採番¶
生成される識別子をテーブルで管理する方法です。 すべてのRDBMSで利用できます。
@TableGenerator では、テーブル名、割り当てサイズ、初期値等を設定できます。 データベースにあらかじめテーブルを定義しておく必要がありますが、 その定義は @TableGenerator の定義とあわせておく必要があります。 デフォルトでは、 ID_GENERATOR という名前のテーブルに、文字列型の PK と数値型の VALUE という2つのカラムが定義されているものとして動作します( PK カラムが主キーです)。 PK カラムにはエンティティクラスごとの一意な名前、 VALUE カラムには識別子の値が格納されます。 テーブルには、エンティティクラスごとのレコードをあらかじめ登録しておく必要があります。
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@TableGenerator(pkColumnValue = "EMPLOYEE_ID")
Integer id;
@TableGenerator の pkColumnValue 要素には、 識別子を管理するテーブル (デフォルトでは、 ID_GENERATOR という名前のテーブル)の主キーの値を指定します。
バージョン¶
楽観的排他制御用のバージョンは @Version を注釈して示します。 フィールドの型は以下のいずれかでなければいけません。
- java.lang.Number のサブタイプ
- java.lang.Number のサブタイプを値とする ドメインクラス
- 上記のいずれかを要素の型とする java.util.Optional
- OptionalInt
- OptionalLong
- OptionalDouble
- 数値のプリミティブ型
@Version
Integer version;
ノート
フィールドの型が エンベッダブルクラス の場合、 @Version は指定できません。
非永続的なフィールド¶
非永続的なフィールドは、テーブルや結果セットのカラムに対応しません。
@Transient を注釈して示します。
フィールドの型や可視性に制限はありません。
@Transient
List<String> nameList;
取得時の状態を管理するフィールド¶
取得時の状態とは、エンティティがDaoから取得されときの全プロパティの値です。 取得時の状態を保持しておくことで、更新処理を実行する際、UPDATE文のSET句に変更したフィールドのみを含められます。 取得時の状態を管理するフィールドは、テーブルや結果セットのカラムに対応しません。
@OriginalStates を注釈して示します。
@OriginalStates
Employee originalStates;
ノート
エンティティクラスのフィールドに エンベッダブルクラス が含まれている場合、 @OriginalStates は使用できません。
Daoインタフェース¶
Data Access Object (Dao) はデータベースアクセスのためのインタフェースです。
デフォルトメソッド¶
デフォルトメソッドでは任意の処理を記述できます。
Config.get にDaoのインスタンスを渡すとDaoに関連づけられた Config インスタンスを取得できます。
@Dao(config = AppConfig.class)
public interface EmployeeDao {
default int count() {
Config config = Config.get(this);
SelectBuilder builder = SelectBuilder.newInstance(config);
builder.sql("select count(*) from employee");
return builder.getScalarSingleResult(int.class);
}
}
利用例¶
コンパイルすると注釈処理により実装クラスが生成されます。 実装クラスをインスタンス化して使用してください。 ただし、設定クラスをDIコンテナで管理する場合、インスタンス化はDIコンテナで制御してください。
EmployeeDao employeeDao = new EmployeeDaoImpl();
Employee employee = employeeDao.selectById(1);
デフォルトでは、実装クラスの名前はインタフェースの名前に Impl をサフィックスしたものになります。 パッケージやサフィックスを変更するには 注釈処理 を参照してください。
デフォルトコンストラクタを使用した場合は、 @Dao の config 要素に指定した設定により DataSource が決定されますが、 特定の DataSource を指定してインスタンス化することも可能です。
DataSource dataSource = ...;
EmployeeDao employeeDao = new EmployeeDaoImpl(dataSource);
Employee employee = employeeDao.selectById(1);
また同様に、 Connection を指定してインスタンス化することも可能です。
Connection connection = ...;
EmployeeDao employeeDao = new EmployeeDaoImpl(connection);
Employee employee = employeeDao.selectById(1);
Daoインタフェースはエンティティクラスと1対1で結びついているわけではありません。 ひとつのDaoインタフェースで複数のエンティティクラスを扱えます。
@Dao(config = AppConfig.class)
public interface MyDao {
@Select
Employee selectEmployeeById(int id);
@Select
Department selectDepartmentByName(String name);
@Update
int updateAddress(Address address);
}
クエリ¶
検索¶
目次
検索を行うには、 @Select をDaoのメソッドに注釈します。
@Config(config = AppConfig.class)
public interface EmployeeDao {
@Select
List<Employee> selectByDepartmentName(String departmentName);
...
}
検索では、 SQLファイルが必須 です。 検索系のSQLを自動生成する機能はありません。
ノート
エンティティクラスを利用する場合、エンティティクラスは 検索結果に応じて 作成する必要があります。 たとえば、EMPLOYEEテーブルに対応するEmployeeエンティティクラスが定義されている場合、 EMPLOYEEテーブルのカラムを含む結果セットはEmployeeエンティティクラスで受けられますが、 EMPLOYEEテーブルとDEPARTMENTテーブルを結合して得られる結果セットに対しては、 Employeeエンティティクラスとは別のクラス(たとえばEmployeeDepartmentクラス)が必要です。
問い合わせ条件¶
問い合わせ条件にはメソッドのパラメータを使用します。 利用できるパラメータの型は以下のものです。
- 基本型
- ドメインクラス
- 任意の型
- 基本型 や ドメインクラス や任意の型を要素とするjava.util.Optional
- 基本型 や ドメインクラス を要素とするjava.util.Iterable
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
パラメータの数に制限はありません。 パラメータの型が 基本型 もしくは ドメインクラス の場合、引数を null にできます。 それ以外の型の場合、引数は null であってはいけません。
基本型やドメインクラスを使った問い合わせ¶
メソッドやパラメータに 基本型 や ドメインクラス を定義します。
@Select
List<Employee> selectByNameAndSalary(String name, Salary salary);
SQLファイルではSQLコメントを使いメソッドのパラメータをSQLにマッピングさせます。 SQLコメントではメソッドのパラメータ名を参照します。
select * from employee where employee_name = /* name */'hoge' and salary > /* salary */100
任意の型を使った問い合わせ¶
メソッドのパラメータに任意の型を使用する場合は、ドット . でフィールドにアクセスしたりメソッドを呼び出すなどしてSQLにマッピングさせます。
@Select
List<Employee> selectByExample(Employee employee);
select * from employee where employee_name = /* employee.name */'hoge' and salary > /* employee.getSalary() */100
パラメータは複数指定できます。
@Select
List<Employee> selectByEmployeeAndDepartment(Employee employee, Department department);
Iterableを使ったIN句へのマッピング¶
java.lang.Iterable のサブタイプは、 IN句を利用した検索を行う場合に使用します。
@Select
List<Employee> selectByNames(List<String> names);
select * from employee where employee_name in /* names */('aaa','bbb','ccc')
1件検索¶
1件を検索するには、メソッドの戻り値の型を次のいずれかにします。
- 基本型
- ドメインクラス
- エンティティクラス
- java.util.Map<String, Object>
- 基本型 、 ドメインクラス 、 エンティティクラス 、 java.util.Map<String, Object> のいずれかを要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
@Select
Employee selectByNameAndSalary(String name, BigDecimal salary);
戻り値の型が Optional でなく、かつ、結果が0件のときは null が返されます。 検索結果の保証 を有効にした場合は、戻り値の型に関係なく結果が0件ならば例外がスローされます。
結果が2件以上存在するときは、 NonUniqueResultException がスローされます。
複数件検索¶
複数件を検索するには、メソッドの戻り値の型を java.util.List にします。 List の要素の型には次のものが使用できます。
- 基本型
- ドメインクラス
- エンティティクラス
- java.util.Map<String, Object>
- 基本型 もしくは ドメインクラス のいずれかを要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
@Select
List<Employee> selectByNameAndSalary(String name, Salary salary);
結果が0件のときは null ではなく空のListが返されます。 ただし、 検索結果の保証 を有効にした場合、結果が0件ならば例外がスローされます。
ストリーム検索¶
全件を一度に java.util.List で受け取るのではなく java.util.stream.Stream で扱いたい場合は、ストリーム検索を利用できます。
ストリーム検索には、 Stream を java.util.Function へ渡す方法と戻り値で返す方法の2種類があります。
Functionへ渡す方法¶
@Select の strategy 要素に SelectType.STREAM を設定し、 メソッドのパラメータに java.util.Function<Stream<TARGET>, RESULT> もしくは java.util.Function<Stream<TARGET>, RESULT> のサブタイプを定義します。
@Select(strategy = SelectType.STREAM)
BigDecimal selectByNameAndSalary(String name, BigDecimal salary, Function<Stream<Employee>, BigDecimal> mapper);
呼び出し元はストリームを受け取って結果を返すラムダ式を渡します。
EmployeeDao dao = new EmployeeDaoImpl();
BigDecimal result = dao.selectByNameAndSalary(name, salary, stream -> {
return ...;
});
Function<Stream<TARGET>, RESULT> の型パラメータ TARGET は次のいずれかでなければいけません。
- 基本型
- ドメインクラス
- エンティティクラス
- java.util.Map<String, Object>
- 基本型 もしくは ドメインクラス のいずれかを要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
型パラメータ RESULT はDaoのメソッドの戻り値に合わせなければいけません。
検索結果の保証 を有効にした場合、結果が0件ならば例外がスローされます。
戻り値で返す方法¶
メソッドの戻り値の型を java.util.stream.Stream にします。 Stream の要素の型には次のものが使用できます。
- 基本型
- ドメインクラス
- エンティティクラス
- java.util.Map<String, Object>
- 基本型 もしくは ドメインクラス のいずれかを要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
@Select
Stream<Employee> selectByNameAndSalary(String name, BigDecimal salary);
呼び出し元です。
EmployeeDao dao = new EmployeeDaoImpl();
try (Stream<Employee> stream = dao.selectByNameAndSalary(name, salary)) {
...
}
検索結果の保証 を有効にした場合、結果が0件ならば例外がスローされます。
警告
リソースの解放漏れを防ぐためにストリームは必ずクローズしてください。 ストリームをクローズしないと、 java.sql.ResultSet 、 java.sql.PreparedStatement 、 java.sql.Connection のクローズが行われません。
ノート
戻り値で返す方法はリソース解放漏れのリスクがあるため、特に理由がない限りは、 Functionへ渡す方法の採用を検討してください。 注意を促すためにDaoのメソッドに対して警告メッセージを表示します。 警告を抑制するには以下のように @Suppress を指定してください。
@Select
@Suppress(messages = { Message.DOMA4274 })
Stream<Employee> selectByNameAndSalary(String name, BigDecimal salary);
コレクト検索¶
検索結果を java.util.Collector で処理したい場合は、コレクト検索を利用できます。
コレクト検索を実施するには、 @Select の strategy 要素に SelectType.COLLECT を設定し、 メソッドのパラメータに java.stream.Collector<TARGET, ACCUMULATION, RESULT> もしくは java.stream.Collector<TARGET, ?, RESULT> のサブタイプを定義します。
@Select(strategy = SelectType.COLLECT)
<RESULT> RESULT selectBySalary(BigDecimal salary, Collector<Employee, ?, RESULT> collector);
呼び出し元は Collector のインスタンスを渡します。
EmployeeDao dao = new EmployeeDaoImpl();
Map<Integer, List<Employee>> result =
dao.selectBySalary(salary, Collectors.groupingBy(Employee::getDepartmentId));
Collector<TARGET, ACCUMULATION, RESULT> の型パラメータ TARGET は次のいずれかでなければいけません。
- 基本型
- ドメインクラス
- エンティティクラス
- java.util.Map<String, Object>
- 基本型 もしくは ドメインクラス のいずれかを要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
型パラメータ RESULT はDaoのメソッドの戻り値に合わせなければいけません。
検索結果の保証 を有効にした場合、結果が0件ならば例外がスローされます。
ノート
コレクト検索はストリーム検索のFunctionに渡す方法のショートカットです。 ストリーム検索で得られる Stream オブジェクトの collect メソッドを使って同等のことができます。
検索オプションを利用した検索¶
検索オプションを表す SelectOptions を使用することで、SELECT文が記述されたSQLファイルをベースにし、 ページング処理や悲観的排他制御用のSQLを自動で生成できます。
SelectOptions は、 1件検索 、 複数件検索 、 ストリーム検索 と組み合わせて使用します。
SelectOptions は、Daoのメソッドのパラメータとして定義します。
@Config(config = AppConfig.class)
public interface EmployeeDao {
@Select
List<Employee> selectByDepartmentName(String departmentName, SelectOptions options);
...
}
SelectOptions のインスタンスは、staticな get メソッドにより取得できます。
SelectOptions options = SelectOptions.get();
ページング¶
SelectOptions の offset メソッドで開始位置、 limit メソッドで取得件数を指定し、 SelectOptions のインスタンスをDaoのメソッドに渡します。
SelectOptions options = SelectOptions.get().offset(5).limit(10);
EmployeeDao dao = new EmployeeDaoImpl();
List<Employee> list = dao.selectByDepartmentName("ACCOUNT", options);
ページングは、ファイルに記述されているオリジナルのSQLを書き換え実行することで実現されています。 オリジナルのSQLは次の条件を満たしていなければいけません。
- SELECT文である
- 最上位のレベルでUNION、EXCEPT、INTERSECT等の集合演算を行っていない(サブクエリで利用している場合は可)
- ページング処理を含んでいない
さらに、データベースの方言によっては特定の条件を満たしていなければいけません。
Dialect | 条件 |
---|---|
Db2Dialect | offsetを指定する場合、ORDER BY句を持ちORDER BY句で指定する カラムすべてをSELECT句に含んでいる |
Mssql2008Dialect | offsetを指定する場合、ORDER BY句を持ちORDER BY句で指定する カラムすべてをSELECT句に含んでいる |
MssqlDialect | offsetを指定する場合、ORDER BY句を持つ必要があります |
StandardDialect | ORDER BY句を持ちORDER BY句で指定する カラムすべてをSELECT句に含んでいる |
悲観的排他制御¶
SelectOptions の forUpdate メソッドで悲観的排他制御を行うことを示し、 SelectOptionsのインスタンスをDaoのメソッドに渡します。
SelectOptions options = SelectOptions.get().forUpdate();
EmployeeDao dao = new EmployeeDaoImpl();
List<Employee> list = dao.selectByDepartmentName("ACCOUNT", options);
SelectOptions には、ロック対象のテーブルやカラムのエイリアスを指定できる forUpdate メソッドや、 ロックの取得を待機しない forUpdateNowait など、名前が forUpdate で始まる悲観的排他制御用のメソッドが用意されています。
悲観的排他制御は、ファイルに記述されているオリジナルのSQLを書き換えて実行しています。 オリジナルのSQLは次の条件を満たしていなければいけません。
- SELECT文である
- 最上位のレベルでUNION、EXCEPT、INTERSECT等の集合演算を行っていない(サブクエリで利用している場合は可)
- 悲観的排他制御の処理を含んでいない
データベースの方言によっては、悲観的排他制御用のメソッドのすべてもしくは一部が使用できません。
Dialect | 説明 |
---|---|
Db2Dialect | forUpdate()を使用できる |
H2Dialect | forUpdate()を使用できる |
HsqldbDialect | forUpdate()を使用できる |
Mssql2008Dialect | forUpdate()とforUpdateNoWait()を使用できる。 ただし、オリジナルのSQLのFROM句は1つのテーブルだけから成らねばならない。 |
MysqlDialect | forUpdate()を使用できる |
OracleDialect | forUpdate()、forUpdate(String... aliases)、 forUpdateNowait()、forUpdateNowait(String... aliases)、 forUpdateWait(int waitSeconds)、 forUpdateWait(int waitSeconds, String... aliases)を使用できる |
PostgresDialect | forUpdate()とforUpdate(String... aliases)を使用できる |
StandardDialect | 悲観的排他制御用のメソッドすべてを使用できない |
集計¶
SelectOptions の count メソッドを呼び出すことで集計件数を取得できるようになります。 通常、ページングのオプションと組み合わせて使用し、ページングで絞り込まない場合の全件数を取得する場合に使います。
SelectOptions options = SelectOptions.get().offset(5).limit(10).count();
EmployeeDao dao = new EmployeeDaoImpl();
List<Employee> list = dao.selectByDepartmentName("ACCOUNT", options);
long count = options.getCount();
集計件数は、Daoのメソッド呼出し後に SelectOptions の getCount メソッドを使って取得します。 メソッド呼び出しの前に count メソッドを実行していない場合、 getCount メソッドは -1 を返します。
検索結果の保証¶
検索結果が1件以上存在することを保証したい場合は、 @Select の ensureResult 要素に true を指定します。
@Select(ensureResult = true)
Employee selectById(Integer id);
検索結果が0件ならば NoResultException がスローされます。
検索結果のマッピングの保証¶
エンティティのプロパティすべてに対して漏れなく結果セットのカラムをマッピングすることを保証したい場合は、 @Select の ensureResultMapping 要素に true を指定します。
@Select(ensureResultMapping = true)
Employee selectById(Integer id);
結果セットのカラムにマッピングされないプロパティが存在する場合 ResultMappingException がスローされます。
クエリタイムアウト¶
@Select の queryTimeout 要素にクエリタイムアウトの秒数を指定できます。
@Select(queryTimeout = 10)
List<Employee> selectAll();
値を指定しない場合、 設定 に指定されたクエリタイムアウトが使用されます。
フェッチサイズ¶
@Select の fetchSize 要素にフェッチサイズを指定できます。
@Select(fetchSize = 20)
List<Employee> selectAll();
値を指定しない場合、 設定 に指定されたフェッチサイズが使用されます。
最大行数¶
@Select の maxRows 要素に最大行数を指定できます。
@Select(maxRows = 100)
List<Employee> selectAll();
値を指定しない場合、 設定 に指定された最大行数が使用されます。
マップのキーのネーミング規約¶
検索結果を java.util.Map<String, Object> にマッピングする場合、 @Select の mapKeyNaming 要素にマップのキーのネーミング規約を指定できます。
@Select(mapKeyNaming = MapKeyNamingType.CAMEL_CASE)
List<Map<String, Object>> selectAll();
MapKeyNamingType.CAMEL_CASE は、カラム名をキャメルケースに変換することを示します。 そのほかにカラム名を大文字や小文字に変換する規約があります。
最終的な変換結果は、ここに指定した値と 設定 に指定された MapKeyNaming の実装により決まります。
SQL のログ出力形式¶
@Select の sqlLog 要素に SQL のログ出力形式を指定できます。
@Select(sqlLog = SqlLogType.RAW)
List<Employee> selectById(Integer id);
SqlLogType.RAW はバインドパラメータ(?)付きの SQL をログ出力することを表します。
挿入¶
挿入を行うには、 @Insert をDaoのメソッドに注釈します。
@Config(config = AppConfig.class)
public interface EmployeeDao {
@Insert
int insert(Employee employee);
@Insert
Result<ImmutableEmployee> insert(ImmutableEmployee employee);
}
デフォルトでは、INSERT文が自動生成されます。 @Insert の sqlFile 要素に true を設定することで、任意のSQLファイルにマッピングできます。
パラメータの エンティティクラス にエンティティリスナーが指定されている場合、 挿入の実行前にエンティティリスナーの preInsert メソッドが呼び出されます。 また、挿入の実行後にエンティティリスナーの postInsert メソッドが呼び出されます。
戻り値¶
パラメータがイミュータブルなエンティティクラスの場合、 戻り値はそのエンティティクラスを要素とする org.seasar.doma.jdbc.Result でなければいけません。
上記の条件を満たさないない場合、戻り値は更新件数を表す int でなければいけません。
SQLの自動生成による挿入¶
パラメータの型はエンティティクラスでなければいけません。 指定できるパラメータの数は1つです。 引数はnullであってはいけません。
@Insert
int insert(Employee employee);
@Insert
Result<ImmutableEmployee> insert(ImmutableEmployee employee);
バージョン番号¶
エンティティクラス に @Version が注釈されたプロパティがある場合、 そのプロパティに明示的に 0 以上の値が設定されていればその値を使用します。 もし設定されていないか、 0 未満の値が設定されていれば 1 を自動で設定します。
挿入対象プロパティの制御¶
insertable¶
エンティティクラスに @Column が注釈されたプロパティがある場合、 @Column の insertable 要素が false のものは挿入対象外です。
exclude¶
@Insert の exclude 要素に指定されたプロパティを挿入対象外とします。 プロパティがこの要素に指定されていれば、 @Column の insertable 要素が true であっても挿入対象外です。
@Insert(exclude = {"name", "salary"})
int insert(Employee employee);
include¶
@Insert の include 要素に指定されたプロパティのみを挿入対象とします。 @Insert の include 要素と exclude 要素の両方に同じプロパティが指定された場合、 そのプロパティは挿入対象外になります。
プロパティがこの要素に指定されていても、 @Column の insertable 要素が false であれば挿入対象外です。
@Insert(include = {"name", "salary"})
int insert(Employee employee);
excludeNull¶
@Insert の excludeNull 要素が true の場合、 値が null のプロパティを挿入対象外とします。 この要素が true の場合、 @Column の insertable 要素が true であったり、 @Insert の include 要素にプロパティが指定されていても、値が null であれば挿入対象外です。
@Insert(excludeNull = true)
int insert(Employee employee);
SQLファイルによる挿入¶
SQLファイルによる挿入を行うには、 @Insert の sqlFile 要素に true を設定し、 メソッドに対応するSQLファイルを用意します。
パラメータには任意の型が使用できます。 指定できるパラメータの数に制限はありません。 パラメータの型が基本型もしくはドメインクラスの場合、引数を null にできます。 それ以外の型の場合、引数は null であってはいけません。
@Insert(sqlFile = true)
int insert(Employee employee);
@Insert(sqlFile = true)
Result<ImmutableEmployee> insert(ImmutableEmployee employee);
たとえば、上記のメソッドに対応するSQLは次のように記述します。
insert into employee (id, name, salary, version)
values (/* employee.id */0,
/* employee.name */'hoge',
/* employee.salary */100,
/* employee.version */0)
SQLファイルによる挿入では、識別子の自動設定やバージョン番号の自動設定は行われません。 また、 @Insert の exclude 要素、 include 要素、 excludeNull 要素は参照されません。
クエリタイムアウト¶
@Insert の queryTimeout 要素にクエリタイムアウトの秒数を指定できます。
@Insert(queryTimeout = 10)
int insert(Employee employee);
この指定は、SQLファイルの使用の有無に関係なく適用されます。 queryTimeout 要素に値を指定しない場合、 設定 に指定されたクエリタイムアウトが使用されます。
SQL のログ出力形式¶
@Insert の sqlLog 要素に SQL のログ出力形式を指定できます。
@Insert(sqlLog = SqlLogType.RAW)
int insert(Employee employee);
SqlLogType.RAW はバインドパラメータ(?)付きの SQL をログ出力することを表します。
更新¶
目次
更新を行うには、 @Update をDaoのメソッドに注釈します。
@Config(config = AppConfig.class)
public interface EmployeeDao {
@Update
int update(Employee employee);
@Update
Result<ImmutableEmployee> update(ImmutableEmployee employee);
}
デフォルトでは、UPDATE文が自動生成されます。 @Update の sqlFile にtrueを設定することで、任意のSQLファイルにマッピングできます。
パラメータのエンティティクラスにエンティティリスナーが指定されている場合、 更新の実行前にエンティティリスナーの preUpdate メソッドを呼び出されます。 また、更新の実行後にエンティティリスナーの postUpdate メソッドを呼び出されます。
戻り値¶
パラメータがイミュータブルなエンティティクラスの場合、 戻り値はそのエンティティクラスを要素とする org.seasar.doma.Result でなければいけません。
上記の条件を満たさないない場合、戻り値は更新件数を表す int でなければいけません。
SQLの自動生成による更新¶
パラメータの型はエンティティクラスでなければいけません。 指定できるパラメータの数は1つです。 引数は null であってはいけません。
@Update
int update(Employee employee);
@Update
Result<ImmutableEmployee> update(ImmutableEmployee employee);
SQL自動生成におけるバージョン番号と楽観的排他制御¶
次の条件を満たす場合に、楽観的排他制御が行われます。
- パラメータのエンティティクラスに@Versionが注釈されたプロパティがある
- @UpdateのignoreVersion要素がfalseである
楽観的排他制御が有効であれば、バージョン番号は識別子とともに更新条件に含まれ、 1増分して更新されます。 このときの更新件数が0件の場合、楽観的排他制御の失敗を示す OptimisticLockException がスローされます。 また、更新件数が0件でない場合、 OptimisticLockException はスローされず、 エンティティのバージョンプロパティの値が1増分されます。
ignoreVersion¶
@Update の ignoreVersion 要素がtrueの場合、 バージョン番号は更新条件には含まれず、UPDATE文のSET句に含まれます。 バージョン番号はアプリケーションで設定した値で更新されます。 この場合、更新件数が0件であっても、 OptimisticLockException はスローされません。
@Update(ignoreVersion = true)
int update(Employee employee);
suppressOptimisticLockException¶
@Update の suppressOptimisticLockException 要素が true の場合、 @Version が注釈されたプロパティがあればバージョン番号は更新条件に含まれ増分もされますが、 更新件数が0件であっても OptimisticLockException はスローされません。 ただし、エンティティのバージョンプロパティの値は1増分されます。
@Update(suppressOptimisticLockException = true)
int update(Employee employee);
更新対象プロパティの制御¶
updatable¶
エンティティクラスに @Column が注釈されたプロパティがある場合、 @Column の updatable 要素がfalseのものは更新対象外です。
exclude¶
@Update の exclude 要素に指定されたプロパティを更新対象外とします。 プロパティがこの要素に指定されていれば、 @Column の updatable 要素が true であっても更新対象外です。
@Update(exclude = {"name", "salary"})
int update(Employee employee);
include¶
@Update の include 要素に指定されたプロパティのみを更新対象とします。 @Update の include 要素と exclude 要素の両方に 同じプロパティが指定された場合、そのプロパティは更新対象外になります。 プロパティがこの要素に指定されていても、 @Column の updatable 要素が false であれば更新対象外です。
@Update(include = {"name", "salary"})
int update(Employee employee);
excludeNull¶
@Update の excludeNull 要素が true の場合、 値が null のプロパティを削除対象外とします。 この要素が true の場合、 @Column の updatable 要素が true であったり、 @Update の include 要素にプロパティが指定されていても、 値が null であれば更新対象外です。
@Update(excludeNull = true)
int update(Employee employee);
includeUnchanged¶
この要素は、更新対象のエンティティクラスに @OriginalStates が注釈されたプロパティがある場合にのみ有効です。
この要素がtrueの場合、エンティティの全プロパティが更新対象となります。 つまり、全プロパティに対応するカラムがUPDATE文のSET句に含まれます。
この要素が false の場合、 エンティティが取得されてから実際に変更されたプロパティのみが更新対象になります。 つまり、変更されたプロパティに対応するカラムのみがUPDATE文のSET句に含まれます。
@Update(includeUnchanged = true)
int update(Employee employee);
SQLファイルによる更新¶
SQLファイルによる更新を行うには、 @Update の sqlFile 要素に true を設定し、 メソッドに対応するSQLファイルを用意します。
ノート
SQLファイルによる更新は、 更新カラムリスト生成コメント の利用有無によりルールが異なります。
更新カラムリスト生成コメントを使用する場合¶
最初のパラメータの型はエンティティクラスでなければいけません。 指定できるパラメータの数に制限はありません。 パラメータの型が基本型もしくはドメインクラスの場合、引数を null にできます。 それ以外の型の場合、引数は null であってはいけません。
@Update(sqlFile = true)
int update(Employee employee, BigDecimal salary);
@Update(sqlFile = true)
Result<ImmutableEmployee> update(ImmutableEmployee employee, , BigDecimal salary);
たとえば、上記のメソッドに対応するSQLは次のように記述します。
update employee set /*%populate*/ id = id where salary > /* salary */0
更新対象プロパティの制御に関するルールは、 SQLの自動生成による更新 と同じです。
更新カラムリスト生成コメントを使用しない場合¶
パラメータには任意の型が使用できます。 指定できるパラメータの数に制限はありません。 パラメータの型が基本型もしくはドメインクラスの場合、引数を null にできます。 それ以外の型の場合、引数は null であってはいけません。
@Update(sqlFile = true)
int update(Employee employee);
@Update(sqlFile = true)
Result<ImmutableEmployee> update(ImmutableEmployee employee);
たとえば、上記のメソッドに対応するSQLは次のように記述します。
update employee set name = /* employee.name */'hoge', salary = /* employee.salary */100
where id = /* employee.id */0
SQLファイルによる更新では、 @Update の exclude 要素、 include 要素、 excludeNull 要素、 includeUnchanged 要素は参照されません。
SQLファイルにおけるバージョン番号と楽観的排他制御¶
次の条件を満たす場合に、楽観的排他制御が行われます。
- パラメータにエンティティクラスを含む
- パラメータの内、左から数えて最初に登場するエンティティクラスに@Versionが注釈されたプロパティがある
- @UpdateのignoreVersion要素がfalseである
ただし、SQLファイルに楽観的排他制御用のSQLを記述するのは、アプリケーション開発者の責任です。 たとえば、下記のSQLのように、 WHERE句でバージョンを番号を指定しSET句でバージョン番号を1だけ増分しなければいけません。
update EMPLOYEE set DELETE_FLAG = 1, VERSION = /* employee.version */1 + 1
where ID = /* employee.id */1 and VERSION = /* employee.version */1
このSQLの更新件数が0件の場合、楽観的排他制御の失敗を示す OptimisticLockException がスローされます。 更新件数が0件でない場合、 OptimisticLockException はスローされず、 エンティティのバージョンプロパティの値が1増分されます。
@Update の ignoreVersion 要素が true の場合、 更新件数が0件であっても、 OptimisticLockException はスローされません。 また、エンティティのバージョンプロパティの値は変更されません。
@Update(sqlFile = true, ignoreVersion = true)
int update(Employee employee);
@Update の suppressOptimisticLockException 要素が true の場合、 更新件数が0件であっても、 OptimisticLockException はスローされません。 ただし、エンティティのバージョンプロパティの値は1増分されます。
@Update(sqlFile = true, suppressOptimisticLockException = true)
int update(Employee employee);
クエリタイムアウト¶
@Update の queryTimeout 要素にクエリタイムアウトの秒数を指定できます。
@Update(queryTimeout = 10)
int update(Employee employee);
この指定はSQLファイルの使用の有無に関係なく適用されます。 queryTimeout 要素に値を指定しない場合、 設定 に指定されたクエリタイムアウトが使用されます。
SQL のログ出力形式¶
@Update の sqlLog 要素に SQL のログ出力形式を指定できます。
@Update(sqlLog = SqlLogType.RAW)
int update(Employee employee);
SqlLogType.RAW はバインドパラメータ(?)付きの SQL をログ出力することを表します。
削除¶
目次
削除を行うには、 @Delete をDaoのメソッドに注釈します。
@Config(config = AppConfig.class)
public interface EmployeeDao {
@Delete
int delete(Employee employee);
}
デフォルトでは、DELETE文が自動生成されます。 @Delete の sqlFile に true を設定することで、任意のSQLファイルにマッピングできます。
パラメータのエンティティクラスにエンティティリスナーが指定されている場合、 削除の実行前にエンティティリスナーの preDelete メソッドが呼び出されます。 また、削除の実行後にエンティティリスナーの postDelete メソッドを呼び出されます。
SQLの自動生成による削除¶
パラメータの型はエンティティクラスでなければいけません。 指定できるパラメータの数は1つです。 引数は null であってはいけません。
@Delete
int delete(Employee employee);
@Delete
Result<ImmutableEmployee> delete(ImmutableEmployee employee);
SQL自動生成におけるバージョン番号と楽観的排他制御¶
次の条件を満たす場合に、楽観的排他制御が行われます。
- パラメータのエンティティクラスに@Versionが注釈されたプロパティがある
- @DeleteのignoreVersion要素がfalseである
楽観的排他制御が有効であれば、バージョン番号は識別子とともに削除条件に含まれます。 この場合、削除件数が0件であれば、楽観的排他制御の失敗を示す OptimisticLockException がスローされます。
ignoreVersion¶
@Delete の ignoreVersion 要素が true の場合、 バージョン番号は削除条件に含まれません。 この場合、削除件数が0件であっても、 OptimisticLockException はスローされません。
@Delete(includeVersion = true)
int delete(Employee employee);
suppressOptimisticLockException¶
@Delete の suppressOptimisticLockException 要素が true の場合、 バージョン番号は削除条件に含まれます。 しかし、この場合、削除件数が0件であっても、 OptimisticLockException はスローされません。
@Delete(suppressOptimisticLockException = true)
int delete(Employee employee);
SQLファイルによる削除¶
SQLファイルによる削除を行うには、 @Delete の sqlFile 要素に true を設定し、 メソッドに対応するSQLファイルを用意します。
パラメータには任意の型が使用できます。 指定できるパラメータの数に制限はありません。 パラメータの型が基本型もしくはドメインクラスの場合、引数を null にできます。 それ以外の型の場合、引数は null であってはいけません。
エンティティにエンティティリスナーが指定されていても、エンティティリスナーのメソッドは呼び出しません。
@Delete(sqlFile = true)
int delete(Employee employee);
たとえば、上記のメソッドに対応するSQLは次のように記述します。
delete from employee where name = /* employee.name */'hoge'
SQLファイルにおけるバージョン番号と楽観的排他制御¶
次の条件を満たす場合に、楽観的排他制御が行われます。
- パラメータにエンティティクラスを含む
- パラメータの内、左から数えて最初に登場するエンティティクラスに@Versionが注釈されたプロパティがある
- @DeleteのignoreVersion要素がfalseである
- @DeleteのsuppressOptimisticLockException要素がfalseである
ただし、SQLファイルに楽観的排他制御用のSQLを記述するのは、アプリケーション開発者の責任です。 たとえば、下記のSQLのように、WHERE句でバージョンを番号を指定しなければいけません。
delete from EMPLOYEE where ID = /* employee.id */1 and VERSION = /* employee.version */1
このSQLの削除件数が0件の場合、楽観的排他制御の失敗を示す OptimisticLockException がスローされます。 削除件数が0件でない場合、 OptimisticLockException はスローされません。
ignoreVersion¶
@Delete の ignoreVersion 要素が true の場合、 削除件数が0件であっても、 OptimisticLockException はスローされません。
@Delete(sqlFile = true, includeVersion = true)
int delete(Employee employee);
suppressOptimisticLockException¶
@Delete の suppressOptimisticLockException 要素が true の場合、 削除件数が0件であっても、 OptimisticLockException はスローされません。
@Delete(sqlFile = true, suppressOptimisticLockException = true)
int delete(Employee employee);
クエリタイムアウト¶
@Delete の queryTimeout 要素にクエリタイムアウトの秒数を指定できます。
@Delete(queryTimeout = 10)
int delete(Employee employee);
この指定は、SQLファイルの使用の有無に関係なく適用されます。 queryTimeout 要素に値を指定しない場合、 設定 に指定されたクエリタイムアウトが使用されます。
SQL のログ出力形式¶
@Delete の sqlLog 要素に SQL のログ出力形式を指定できます。
@Delete(sqlLog = SqlLogType.RAW)
int delete(Employee employee);
SqlLogType.RAW はバインドパラメータ(?)付きの SQL をログ出力することを表します。
バッチ挿入¶
目次
バッチ挿入を行うには、 @BatchInsert をDaoのメソッドに注釈します。
@Config(config = AppConfig.class)
public interface EmployeeDao {
@BatchInsert
int[] insert(List<Employee> employees);
@BatchInsert
BatchResult<ImmutableEmployee> insert(List<ImmutableEmployee> employees);
}
デフォルトでは、INSERT文が自動生成されます。 @BatchInsert の sqlFile に true を設定することで、任意のSQLファイルにマッピングできます。
パラメータの要素のに エンティティクラス が指定されている場合、 挿入の実行前にエンティティリスナーの preInsert メソッドをエンティティごとに呼び出します。 また、挿入の実行後にエンティティリスナーの postInsert メソッドをエンティティごとに呼び出します。
戻り値¶
パラメータ Iterable のサブタイプの要素がイミュータブルなエンティティクラスの場合、 戻り値はそのエンティティクラスを要素とする org.seasar.doma.BatchResult でなければいけません。
上記の条件を満たさないない場合、戻り値は各更新処理の更新件数を表す int[] でなければいけません。
SQLの自動生成によるバッチ挿入¶
パラメータの型は エンティティクラス 要素とする java.lang.Iterable のサブタイプでなければいけません。 指定できるパラメータの数は1つです。 引数は null であってはいけません。 戻り値の配列の要素の数はパラメータの Iterable の要素の数と等しくなります。 配列のそれぞれの要素が更新された件数を返します。
バージョン番号¶
エンティティクラス に @Version が注釈されたプロパティがある場合、 そのプロパティに明示的に 0 以上の値が設定されていればその値を使用します。 もし設定されていないか、 0 未満の値が設定されていれば 1 を自動で設定します。
挿入対象プロパティ¶
exclude¶
@BatchInsert の exclude 要素に指定されたプロパティを挿入対象外とします。 プロパティがこの要素に指定されていれば、 @Column の insertable 要素が true であっても挿入対象外です。
@BatchInsert(exclude = {"name", "salary"})
int[] insert(List<Employee> employees);
include¶
@BatchInsert の include 要素に指定されたプロパティのみを挿入対象とします。 @BatchInsert の include 要素と exclude 要素の両方に同じプロパティが指定された場合、 そのプロパティは挿入対象外になります。 プロパティがこの要素に指定されていても、 @Column の insertable 要素が false であれば挿入対象外です。
@BatchInsert(include = {"name", "salary"})
int[] insert(List<Employee> employees);
SQLファイルによるバッチ挿入¶
SQLファイルによるバッチ挿入を行うには、 @BatchInsert の sqlFile 要素に true を設定し、 メソッドに対応するSQLファイルを用意します。
@BatchInsert(sqlFile = true)
int[] insert(List<Employee> employees);
@BatchInsert(sqlFile = true)
BatchResult<ImmutableEmployee> insert(List<ImmutableEmployee> employees);
パラメータは任意の型を要素とする java.lang.Iterable のサブタイプでなければいけません。 指定できるパラメータの数は1つです。 引数は null であってはいけません。 戻り値の配列の要素の数はパラメータの Iterable の要素の数と等しくなります。 配列のそれぞれの要素が更新された件数を返します。
エンティティクラス にエンティティリスナーが指定されていても、エンティティリスナーのメソッドは呼び出しません。
たとえば、上記のメソッドに対応するSQLは次のように記述します。
insert into employee (id, name, salary, version)
values (/* employees.id */0, /* employees.name */'hoge', /* employees.salary */100, /* employees.version */0)
SQLファイル上では、パラメータの名前は java.lang.Iterable のサブタイプの要素を指します。
SQLファイルによるバッチ挿入では、識別子の自動設定やバージョン番号の自動設定は行われません。 また、 @BatchInsert の exclude 要素、 include 要素は参照されません。
クエリタイムアウト¶
@BatchInsert の queryTimeout 要素にクエリタイムアウトの秒数を指定できます。
@BatchInsert(queryTimeout = 10)
int[] insert(List<Employee> employees);
この指定は、SQLファイルの使用の有無に関係なく適用されます。 queryTimeout 要素に値を指定しない場合、 設定 に指定されたクエリタイムアウトが使用されます。
バッチサイズ¶
@BatchInsert の batchSize 要素にバッチサイズを指定できます。
@BatchInsert(batchSize = 10)
int[] insert(List<Employee> employees);
この指定は、SQLファイルの使用の有無に関係なく適用されます。 batchSize 要素に値を指定しない場合、 設定 に指定されたバッチサイズが使用されます。
SQL のログ出力形式¶
@BatchInsert の sqlLog 要素に SQL のログ出力形式を指定できます。
@BatchInsert(sqlLog = SqlLogType.RAW)
int insert(Employee employee);
SqlLogType.RAW はバインドパラメータ(?)付きの SQL をログ出力することを表します。
バッチ更新¶
目次
バッチ更新を行うには、 @BatchUpdate をDaoのメソッドに注釈します。
@Config(config = AppConfig.class)
public interface EmployeeDao {
@BatchUpdate
int[] update(List<Employee> employees);
@BatchUpdate
BatchResult<ImmutableEmployee> update(List<ImmutableEmployee> employees);
}
デフォルトでは、UPDATE文が自動生成されます。 @BatchUpdate の sqlFile に true を設定することで、任意のSQLファイルにマッピングできます。
パラメータの要素の エンティティクラス にエンティティリスナーが指定されている場合、 更新の実行前にエンティティリスナーの preUpdate メソッドがエンティティごとに呼び出されます。 また、更新の実行後にエンティティリスナーの postUpdate メソッドがエンティティごとに呼び出されます。
戻り値¶
パラメータ Iterable のサブタイプの要素がイミュータブルなエンティティクラスの場合、 戻り値はそのエンティティクラスを要素とする org.seasar.doma.BatchResult でなければいけません。
上記の条件を満たさないない場合、戻り値は各更新処理の更新件数を表す int[] でなければいけません。
SQLの自動生成によるバッチ更新¶
パラメータの型は エンティティクラス を要素とする java.lang.Iterable のサブタイプでなければいけません。 指定できるパラメータの数は1つです。 引数は null であってはいけません。 戻り値の配列の要素の数はパラメータの Iterable の要素の数と等しくなります。 配列のそれぞれの要素が更新された件数を返します。
SQL自動生成におけるバージョン番号と楽観的排他制御¶
次の条件を満たす場合に、楽観的排他制御が行われます。
- パラメータのjava.lang.Iterableのサブタイプの要素である エンティティクラス に@Versionが注釈されたプロパティがある
- @BatchUpdateのignoreVersion要素がfalseである
楽観的排他制御が有効であれば、バージョン番号は識別子とともに更新条件に含まれ、 1増分して更新されます。 このときの更新件数が0件の場合、楽観的排他制御の失敗を示す BatchOptimisticLockException がスローされます。 一方、更新件数が1件の場合は、 BatchOptimisticLockException はスローされず、 エンティティのバージョンプロパティの値が1増分されます。
ignoreVersion¶
@BatchUpdate の ignoreVersion 要素が true の場合、 バージョン番号は更新条件には含まれず、UPDATE文のSET句に含まれます。 バージョン番号はアプリケーションで設定した値で更新されます。 この場合、更新件数が0件であっても、 BatchOptimisticLockException はスローされません。
@BatchUpdate(ignoreVersion = true)
int[] update(List<Employee> employees);
suppressOptimisticLockException¶
@BatchUpdate の suppressOptimisticLockException 要素が true の場合、 @Versioni が注釈されたプロパティがあればバージョン番号は更新条件に含まれ増分もされますが、 更新件数が0件であっても BatchOptimisticLockException はスローされません。 ただし、エンティティのバージョンプロパティの値は1増分されます。
@BatchUpdate(suppressOptimisticLockException = true)
int[] update(List<Employee> employees);
更新対象プロパティ¶
exclude¶
@BatchUpdate の exclude 要素に指定されたプロパティを更新対象外とします。 プロパティがこの要素に指定されていれば、 @Column の updatable 要素が true であっても削除対象外です。
@BatchUpdate(exclude = {"name", "salary"})
int[] update(List<Employee> employees);
include¶
@BatchUpdate の include 要素に指定されたプロパティのみを削除対象とします。 @BatchUpdate の include 要素と exclude 要素の両方に同じプロパティが指定された場合、そのプロパティは更新対象外になります。 プロパティがこの要素に指定されていても、 @Column の updatable 要素が false であれば更新対象外です。
@BatchUpdate(include = {"name", "salary"})
int[] update(List<Employee> employees);
SQLファイルによるバッチ更新¶
SQLファイルによるバッチ更新を行うには、 @BatchUpdate の sqlFile 要素に true を設定し、 メソッドに対応するSQLファイルを用意します。
ノート
SQLファイルによるバッチ更新は、 更新カラムリスト生成コメント の利用有無によりルールが異なります。
更新カラムリスト生成コメントを使用する場合¶
@BatchUpdate(sqlFile = true)
int[] update(List<Employee> employees);
@BatchUpdate
BatchResult<ImmutableEmployee> update(List<ImmutableEmployee> employees);
パラメータの型は エンティティクラス を要素とする java.lang.Iterable のサブタイプでなければいけません。 指定できるパラメータの数は1つです。 引数は null であってはいけません。 戻り値の配列の要素の数はパラメータの Iterable の要素の数と等しくなります。 配列のそれぞれの要素が更新された件数を返します。
たとえば、上記のメソッドに対応するSQLは次のように記述します。
update employee set /*%populate*/ id = id where name = /* employees.name */'hoge'
SQLファイル上では、パラメータの名前は Iterable のサブタイプの要素を指します。
更新対象プロパティの制御に関するルールは、 SQLの自動生成によるバッチ更新 と同じです。
更新カラムリスト生成コメントを使用しない場合¶
@BatchUpdate(sqlFile = true)
int[] update(List<Employee> employees);
@BatchUpdate
BatchResult<ImmutableEmployee> update(List<ImmutableEmployee> employees);
パラメータは任意の型を要素とする java.lang.Iterable のサブタイプでなければいけません。 指定できるパラメータの数は1つです。 引数は null であってはいけません。 戻り値の配列の要素の数はパラメータの Iterable の要素の数と等しくなります。 配列のそれぞれの要素が更新された件数を返します。
たとえば、上記のメソッドに対応するSQLは次のように記述します。
update employee set name = /* employees.name */'hoge', salary = /* employees.salary */100
where id = /* employees.id */0
SQLファイル上では、パラメータの名前は Iterable のサブタイプの要素を指します。
SQLファイルによるバッチ更新では、バージョン番号の自動更新は行われません。 また、 @BatchUpdate の exclude 要素、 include 要素は参照されません。
SQLファイルにおけるバージョン番号と楽観的排他制御¶
次の条件を満たす場合に、楽観的排他制御が行われます。
- パラメータのjava.lang.Iterableのサブタイプの要素が エンティティクラス であり、 エンティティクラス に@Versionが注釈されたプロパティがある
- @BatchUpdateのignoreVersion要素がfalseである
ただし、SQLファイルに楽観的排他制御用のSQLを記述するのは、アプリケーション開発者の責任です。 たとえば、下記のSQLのように、 WHERE句でバージョンを番号を指定しSET句でバージョン番号を1だけ増分しなければいけません。
update EMPLOYEE set DELETE_FLAG = 1, VERSION = /* employees.version */1 + 1
where ID = /* employees.id */1 and VERSION = /* employees.version */1
このSQLの更新件数が0件または複数件の場合、楽観的排他制御の失敗を示す BatchOptimisticLockException がスローされます。 更新件数が1件の場合、 BatchOptimisticLockException はスローされず、 エンティティのバージョンプロパティの値が1増分されます。
楽観的排他制御が有効であれば、バージョン番号は識別子とともに更新条件に含まれ、 1増分して更新されます。 このときの更新件数が0件または複数件の場合、楽観的排他制御の失敗を示す BatchOptimisticLockException がスローされます。 一方、更新件数が1件の場合、 BatchOptimisticLockException はスローされず、エンティティのバージョンプロパティの値が1増分されます。
@BatchUpdate の ignoreVersion 要素が true の場合、 更新件数が0件または複数件であっても、 BatchOptimisticLockException はスローされません。 また、エンティティのバージョンプロパティの値は変更されません。
@BatchUpdate(sqlFile = true, ignoreVersion = true)
int[] update(List<Employee> employees);
@BatchUpdate の suppressOptimisticLockException 要素が true の場合、 更新件数が0件または複数件であっても BatchOptimisticLockException はスローされません。 ただし、エンティティのバージョンプロパティの値は1増分されます。
@BatchUpdate(sqlFile = true, suppressOptimisticLockException = true)
int[] update(List<Employee> employees);
クエリタイムアウト¶
@BatchUpdate の queryTimeout 要素にクエリタイムアウトの秒数を指定できます。
@BatchUpdate(queryTimeout = 10)
int[] update(List<Employee> employees);
この設定は、SQLファイルの使用の有無に関係なく適用されます。 queryTimeout 要素に値を指定しない場合、 設定クラスに指定されたクエリタイムアウトが使用されます。
バッチサイズ¶
@BatchUpdate の batchSize 要素にバッチサイズを指定できます。
@BatchUpdate(batchSize = 10)
int[] update(List<Employee> employees);
この指定は、SQLファイルの使用の有無に関係なく適用されます。 batchSize 要素に値を指定しない場合、 設定 クラスに指定されたバッチサイズが使用されます。
SQL のログ出力形式¶
@BatchUpdate の sqlLog 要素に SQL のログ出力形式を指定できます。
@BatchUpdate(sqlLog = SqlLogType.RAW)
int[] update(List<Employee> employees);
SqlLogType.RAW はバインドパラメータ(?)付きの SQL をログ出力することを表します。
バッチ削除¶
目次
バッチ削除を行うには、 @BatchDelete をDaoのメソッドに注釈します。
@Config(config = AppConfig.class)
public interface EmployeeDao {
@BatchDelete
int[] delete(List<Employee> employees);
...
}
デフォルトでは、DELETE文が自動生成されます。 @BatchDelete の sqlFile に true を設定することで、任意のSQLファイルにマッピングできます。
パラメータの要素である エンティティクラス にエンティティリスナーが指定されている場合、 削除の実行前にエンティティリスナーの preDelete メソッドがエンティティごとに呼び出されます。 また、削除の実行後にエンティティリスナーの postDelete メソッドがエンティティごとに呼び出されます。
SQLの自動生成によるバッチ削除¶
パラメータの型は エンティティクラス を要素とする java.lang.Iterable のサブタイプでなければいけません。 指定できるパラメータの数は1つです。 引数は null であってはいけません。 戻り値の配列の要素の数はパラメータの Iterable の要素の数と等しくなります。 配列のそれぞれの要素が更新された件数を返します。
SQL自動生成におけるバージョン番号と楽観的排他制御¶
次の条件を満たす場合に、楽観的排他制御が行われます。
- パラメータのjava.lang.Iterableのサブタイプの要素である エンティティクラス に@Versionが注釈されたプロパティがある
- @BatchDeleteのignoreVersion要素がfalseである
楽観的排他制御が有効であれば、バージョン番号は識別子とともに削除条件に含まれます。 この場合、削除件数が0件であれば、楽観的排他制御の失敗を示す BatchOptimisticLockException がスローされます。
ignoreVersion¶
@BatchDelete の ignoreVersion 要素が true の場合、 バージョン番号は削除条件には含まれません。 削除件数が0件であっても BatchOptimisticLockException はスローされません。
@BatchDelete(ignoreVersion = true)
int[] delete(List<Employee> employees);
suppressOptimisticLockException¶
@BatchDelete の suppressOptimisticLockException 要素が true の場合、 バージョン番号は削除条件に含まれますが、 削除件数が0件であっても BatchOptimisticLockException はスローされません。
@BatchDelete(suppressOptimisticLockException = true)
int[] delete(List<Employee> employees);
SQLファイルによるバッチ削除¶
SQLファイルによるバッチ削除を行うには、 @BatchDelete の sqlFile 要素に true を設定し、 メソッドに対応するSQLファイルを用意します。
@BatchDelete(sqlFile = true)
int[] delete(List<Employee> employees);
パラメータは任意の型を要素とする java.lang.Iterable のサブタイプでなければいけません。 指定できるパラメータの数は1つです。 引数は null であってはいけません。 戻り値の配列の要素の数はパラメータの Iterable の要素の数と等しくなります。 配列のそれぞれの要素が更新された件数を返します。
たとえば、上記のメソッドに対応するSQLは次のように記述します。
delete from employee where name = /* employees.name */'hoge'
SQLファイル上では、パラメータの名前は java.lang.Iterable のサブタイプの要素を指します。
SQLファイルにおけるバージョン番号と楽観的排他制御¶
次の条件を満たす場合に、楽観的排他制御が行われます。
- パラメータの java.lang.Iterable のサブタイプの要素が エンティティクラス であり、 エンティティクラス に@Versionが注釈されたプロパティがある
- @BatchDeleteのignoreVersion要素がfalseである
ただし、SQLファイルに楽観的排他制御用のSQLを記述するのは、アプリケーション開発者の責任です。 たとえば、下記のSQLのように、WHERE句でバージョンを番号を指定しなければいけません。
delete from EMPLOYEE where ID = /* employees.id */1 and VERSION = /* employees.version */1
このSQLの削除件数が0件または複数件の場合、 楽観的排他制御の失敗を示す BatchOptimisticLockException がスローされます。
ignoreVersion¶
@BatchDelete の ignoreVersion 要素が true の場合、削除件数が0件または複数件であっても BatchOptimisticLockException はスローされません。
@BatchDelete(sqlFile = true, ignoreVersion = true)
int[] delete(List<Employee> employees);
suppressOptimisticLockException¶
@BatchDelete の suppressOptimisticLockException 要素が true の場合、削除件数が0件または複数件であっても BatchOptimisticLockException はスローされません。
@BatchDelete(sqlFile = true, suppressOptimisticLockException = true)
int[] delete(List<Employee> employees);
クエリタイムアウト¶
@BatchDelete の queryTimeout 要素にクエリタイムアウトの秒数を指定できます。
@BatchDelete(queryTimeout = 10)
int[] delete(List<Employee> employees);
この指定は、SQLファイルの使用の有無に関係なく適用されます。 queryTimeout 要素に値を指定しない場合、 設定 に指定されたクエリタイムアウトが使用されます。
バッチサイズ¶
@BatchDelete の batchSize 要素にバッチサイズを指定できます。
@BatchDelete(batchSize = 10)
int[] delete(List<Employee> employees);
この設定は、SQLファイルの使用の有無に関係なく適用されます。 batchSize 要素に値を指定しない場合、 設定 に指定されたバッチサイズが使用されます。
SQL のログ出力形式¶
@BatchDelete の sqlLog 要素に SQL のログ出力形式を指定できます。
@BatchDelete(sqlLog = SqlLogType.RAW)
int[] delete(List<Employee> employees);
SqlLogType.RAW はバインドパラメータ(?)付きの SQL をログ出力することを表します。
ストアドファンクション¶
目次
ストアドファンクションを呼び出すには、 @Function をDaoのメソッドに注釈します。
@Config(config = AppConfig.class)
public interface EmployeeDao {
@Function
Integer execute(@In Integer id, @InOut Reference<BigDecimal> salary);
...
}
パラメータには、パラメータの種別を示す @In 、 @InOut 、 @Out 、 @ResultSet のいずれかのアノテーションが必須です。 パラメータは複数指定できます。
ファンクション名¶
デフォルトではメソッド名がファンクション名になります。 @Function の name 要素に値を指定した場合は、その値がファンクション名になります。
@Function(name = "calculateSalary")
void execute(@In Integer id, @InOut Reference<BigDecimal> salary);
@Function の catalog 要素や schema 要素にカタログ名やスキーマ名を指定できます。 このときファンクションの名前は catalog 要素、 schema 要素、 name 要素 (指定されていなければメソッド名)をピリオドで連結したものになります。
@Function(catlog = "CATALOG", schema ="SCHEMA", name = "calculateSalary")
void execute(@In Integer id, @InOut Reference<BigDecimal> salary);
戻り値の型が エンティティクラス や エンティティクラス を要素とする java.util.List の場合において、 エンティティのプロパティすべてに対して漏れなく結果セットのカラムをマッピングすることを保証したい場合は、 @Function の ensureResultMapping 要素に true を指定します。
@Function(ensureResultMapping = true)
List<Employee> execute();
結果セットのカラムにマッピングされないプロパティが存在する場合 ResultMappingException がスローされます。
パラメータ¶
ストアドファンクションのパラメータとDaoメソッドのパラメータの並び順は合わせなければいけません。
INパラメータ¶
INパラメータは、 @In をメソッドのパラメータに注釈して示します。 指定可能なパラメータの型は以下の通りです。
- 基本型
- ドメインクラス
- 基本型 または ドメインクラス を要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
パラメータの型が基本型もしくはドメインクラスの場合、引数を null にできます。 それ以外の型の場合、引数は null であってはいけません。
@Function
void execute(@In Integer id);
次のように使用します。
EmployeeDao dao = new EmployeeDaoImpl();
dao.execute(1);
INOUTパラメータ¶
INOUTパラメータは、 @InOut をメソッドのパラメータに注釈して示します。 注釈されるパラメータの型は org.seasar.doma.jdbc.Reference でなければいけません。 Reference の型パラメータに指定できる型は以下の通りです。
- 基本型
- ドメインクラス
- 基本型 または ドメインクラス を要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
引数は null であってはいけません。
@Function
void execute(@InOut Reference<BigDecimal> salary);
次のように使用します。
EmployeeDao dao = new EmployeeDaoImpl();
BigDecimal in = new BigDecimal(100);
Reference<BigDecimal> ref = new Reference<BigDecimal>(in);
dao.execute(ref);
BigDecimal out = ref.get();
OUTパラメータ¶
OUTパラメータは、 @Out をメソッドのパラメータに注釈して示します。 注釈されるパラメータの型は org.seasar.doma.jdbc.Reference でなければいけません。 Reference の型パラメータに指定できる型は以下の通りです。
- 基本型
- ドメインクラス
- 基本型 または ドメインクラス を要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
引数は null であってはいけません。
メソッドの戻り値の型が void 以外の場合、戻り値はOUTパラメータとなります。
@Function
Integer execute(@Out Reference<BigDecimal> salary);
次のように使用します。
EmployeeDao dao = new EmployeeDaoImpl();
Reference<BigDecimal> ref = new Reference<BigDecimal>();
Integer result = dao.execute(ref);
BigDecimal out = ref.get();
カーソルのOUTパラメータもしくは結果セット¶
カーソルのOUTパラメータ、もしくはストアドファンクションが返す結果セットは、 @ResultSet をメソッドのパラメータに注釈して示します。 注釈されるパラメータの型は、以下の型を要素とする java.util.List でなければいけません。
- 基本型
- ドメインクラス
- エンティティクラス
- java.util.Map<String, Object>
- 基本型 または ドメインクラス を要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
引数は null であってはいけません。
@Function
void execute(@ResultSet List<Employee> employee);
次のように使用します。
EmployeeDao dao = new EmployeeDaoImpl();
List<Employee> employees = new ArrayList<Employee>();
dao.execute(employees);
for (Employee e : employees) {
...
}
@ResultSet が注釈された java.util.List の型パラメータが エンティティクラス であり、かつ、エンティティのプロパティすべてに対して 漏れなく結果セットのカラムをマッピングすることを保証したい場合は、 @ResultSet の ensureResultMapping 要素に true を指定します。
@Function
void execute(@ResultSet(ensureResultMapping = true) List<Employee> employee);
結果セットのカラムにマッピングされないプロパティが存在する場合 ResultMappingException がスローされます。
マップのキーのネーミング規約¶
結果セットを java.util.Map<String, Object> にマッピングする場合、 @Function の mapKeyNaming 要素にマップのキーのネーミング規約を指定できます。
@Function(mapKeyNaming = MapKeyNamingType.CAMEL_CASE)
List<Map<String, Object>> execute(@In Integer id);
MapKeyNamingType.CAMEL_CASE は、カラム名をキャメルケースに変換することを示します。 そのほかに、カラム名をを大文字や小文字に変換する規約があります。
最終的な変換結果は、ここに指定した値と 設定 に指定された MapKeyNaming の実装により決まります。
SQL のログ出力形式¶
@Function の sqlLog 要素に SQL のログ出力形式を指定できます。
@Function(sqlLog = SqlLogType.RAW)
void execute(@In Integer id);
SqlLogType.RAW はバインドパラメータ(?)付きの SQL をログ出力することを表します。
ストアドプロシージャー¶
目次
ストアドプロシージャーを呼び出すには、 @Procedure をDaoのメソッドに注釈します。
@Config(config = AppConfig.class)
public interface EmployeeDao {
@Procedure
void execute(@In Integer id, @InOut Reference<BigDecimal> salary);
...
}
パラメータには、パラメータの種別を示す @In 、 @InOut 、 @Out 、 @ResultSet のいずれかのアノテーションが必須です。 パラメータは複数指定できます。
プロシージャー名¶
デフォルトではメソッド名がプロシージャー名になります。 @Procedure の name 要素に値を指定した場合は、その値がプロシージャー名になります。
@Procedure(name = "calculateSalary")
void execute(@In Integer id, @InOut Reference<BigDecimal> salary);
@Procedure の catalog 要素や schema 要素にカタログ名やスキーマ名を指定できます。 このときプロシージャーの名前は catalog 要素、 schema 要素、 name 要素(指定されていなければメソッド名)をピリオドで連結したものになります。
@Procedure(catlog = "CATALOG", schema ="SCHEMA", name = "calculateSalary")
void execute(@In Integer id, @InOut Reference<BigDecimal> salary);
パラメータ¶
ストアドプロシージャーのパラメータとDaoメソッドのパラメータの並び順は合わせなければいけません。
INパラメータ¶
INパラメータは、 @In をメソッドのパラメータに注釈して示します。 指定可能なパラメータの型は以下の通りです。
- 基本型
- ドメインクラス
- 基本型 または ドメインクラス を要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
パラメータの型が基本型もしくはドメインクラスの場合、引数を null にできます。 それ以外の型の場合、引数は null であってはいけません。
@Procedure
void execute(@In Integer id);
次のように使用します。
EmployeeDao dao = new EmployeeDaoImpl();
dao.execute(1);
INOUTパラメータ¶
INOUTパラメータは、 @InOut をメソッドのパラメータに注釈して示します。 注釈されるパラメータの型は org.seasar.doma.jdbc.Reference でなければいけません。 Reference の型パラメータに指定できる型は以下の通りです。
- 基本型
- ドメインクラス
- 基本型 または ドメインクラス を要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
引数は null であってはいけません。
@Procedure
void execute(@InOut Reference<BigDecimal> salary);
次のように使用します。
EmployeeDao dao = new EmployeeDaoImpl();
BigDecimal in = new BigDecimal(100);
Reference<BigDecimal> ref = new Reference<BigDecimal>(in);
dao.execute(ref);
BigDecimal out = ref.get();
OUTパラメータ¶
OUTパラメータは、 @Out をメソッドのパラメータに注釈して示します。 注釈されるパラメータの型は org.seasar.doma.jdbc.Reference でなければいけません。 Reference の型パラメータに指定できる型は以下の通りです。
- 基本型
- ドメインクラス
- 基本型 または ドメインクラス を要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
引数は null であってはいけません。
@Procedure
void execute(@Out Reference<BigDecimal> salary);
次のように使用します。
EmployeeDao dao = new EmployeeDaoImpl();
Reference<BigDecimal> ref = new Reference<BigDecimal>();
dao.execute(ref);
BigDecimal out = ref.get();
カーソルのOUTパラメータもしくは結果セット¶
カーソルのOUTパラメータ、もしくはストアドプロシージャーが返す結果セットは、 @ResultSet をメソッドのパラメータに注釈して示します。 注釈されるパラメータの型は、以下の型を要素とする java.util.List でなければいけません。
- 基本型
- ドメインクラス
- エンティティクラス
- java.util.Map<String, Object>
- 基本型 または ドメインクラス を要素とするjava.util.Optional
- java.util.OptionalInt
- java.util.OptionalLong
- java.util.OptionalDouble
引数は null であってはいけません。
@Procedure
void execute(@ResultSet List<Employee> employees);
次のように使用します。
EmployeeDao dao = new EmployeeDaoImpl();
List<Employee> employees = new ArrayList<Employee>();
dao.execute(employees);
for (Employee e : employees) {
...
}
@ResultSet が注釈された java.util.List の型パラメータが エンティティクラス であり、かつ、エンティティのプロパティすべてに対して 漏れなく結果セットのカラムをマッピングすることを保証したい場合は、 @ResultSet の ensureResultMapping 要素に true を指定します。
@Procedure
void execute(@ResultSet(ensureResultMapping = true) List<Employee> employee);
結果セットのカラムにマッピングされないプロパティが存在する場合 ResultMappingException がスローされます。
マップのキーのネーミング規約¶
結果セットを java.util.Map<String, Object> にマッピングする場合、 @Procedure の mapKeyNaming 要素にマップのキーのネーミング規約を指定できます。
@Procedure(mapKeyNaming = MapKeyNamingType.CAMEL_CASE)
void execute(@ResultSet List<Map<String, Object>> employees);
MapKeyNamingType.CAMEL_CASE は、カラム名をキャメルケースに変換することを示します。 そのほかに、カラム名をを大文字や小文字に変換する規約があります。
最終的な変換結果は、ここに指定した値と 設定 に指定された MapKeyNaming の実装により決まります。
SQL のログ出力形式¶
@Procedure の sqlLog 要素に SQL のログ出力形式を指定できます。
@Procedure(sqlLog = SqlLogType.RAW)
void execute(@In Integer id);
SqlLogType.RAW はバインドパラメータ(?)付きの SQL をログ出力することを表します。
ファクトリ¶
java.sql.Connection が提供するファクトリメソッドからインスタンスを取得するには、 Daoのメソッドに次のアノテーションを注釈します。
- java.sql.Arrayを生成するには、@ArrayFactory
- java.sql.Blobを生成するには、@BlobFactory
- java.sql.Clobを生成するには、@ClobFactory
- java.sql.NClobを生成するには、@NClobFactory
- java.sql.SQLXMLを生成するには、@SQLXMLFactory
スクリプト¶
SQLスクリプトの実行を行うには、 @Script をDaoのメソッドに注釈します。
@Config(config = AppConfig.class)
public interface EmployeeDao {
@Script
void createTable();
...
}
メソッドの戻り値の型は void でなければいけません。パラメータの数は0でなければいけません。
また、メソッドに対応するスクリプトファイルが必須です。
スクリプトファイル¶
スクリプトファイルでは、 Dialect が提供するRDBMS名や区切り文字が使用されます。
データベース | Dialectの名前 | RDBMS名 | 区切り文字 |
---|---|---|---|
DB2 | Db2Dialect | db2 | @ |
H2 Database Engine 1.2.126 | H212126Dialect | h2 | |
H2 Database | H2Dialect | h2 | |
HSQLDB | HsqldbDialect | hsqldb | |
Microsoft SQL Server 2008 | Mssql2008Dialect | mssql | GO |
Microsoft SQL Server | MssqlDialect | mssql | GO |
MySQL | MySqlDialect | mysql | / |
Oracle Database | OracleDialect | oracle | / |
PostgreSQL | PostgresDialect | postgres | $$ |
SQLite | SqliteDialect | sqlite |
ファイル名の形式¶
ファイル名は、次の形式でなければいけません。
META-INF/Daoのクラスの完全修飾名をディレクトリに変換したもの/Daoのメソッド名.script
例えば、 Daoのクラスが aaa.bbb.EmployeeDao でマッピングしたいメソッドが createTable の場合、パス名は次のようになります。
META-INF/aaa/bbb/EmployeeDao/createTable.script
複数のRDBMSに対応する必要があり特定のRDBMSでは別のスクリプトファイルを使いたい場合、 .script の前にハイフン区切りでRDBMS名を入れることで、 優先的に使用するファイルを指示できます。 たとえば、PostgreSQL専用のSQLファイルは次の名前にします。
META-INF/aaa/bbb/EmployeeDao/createTables-postgres.script
この場合、PostgreSQLを使用している場合に限り、 META-INF/aaa/bbb/EmployeeDao/createTable.script よりも META-INF/aaa/bbb/EmployeeDao/createTable-postgres.script が優先的に使用されます。
区切り文字¶
スクリプトファイルの区切り文字には、 ステートメントの区切り文字とブロックの区切り文字の2種類があります。
ステートメントの区切り文字はセミコロン ; です。
ブロックの区切り文字は、 Dialect が提供する値が使用されます。
ブロックの区切り文字は、アノテーションの blockDelimiter 要素で明示することもできます。 アノテーションで指定した場合、 Dialect の値よりも優先されます。
@Script(blockDelimiter = "GO")
void createTable();
エラー発生時の継続実行¶
デフォルトでは、スクリプト中のどれかのSQLの実行が失敗すれば、 処理はそこで止まります。 しかし、アノテーションの haltOnError 要素に false を指定することで、エラー発生時に処理を継続させることができます。
@Script(haltOnError = false)
void createTable();
記述例¶
スクリプトファイルは次のように記述できます。 この例は、Oracle Databaseに有効なスクリプトです。
/*
* テーブル定義(SQLステートメント)
*/
create table EMPLOYEE (
ID numeric(5) primary key, -- 識別子
NAME varchar2(20) -- 名前
);
/*
* データの追加(SQLステートメント)
*/
insert into EMPLOYEE (ID, NAME) values (1, 'SMITH');
/*
* プロシージャー定義(SQLブロック)
*/
create or replace procedure proc
( cur out sys_refcursor,
employeeid in numeric
) as
begin
open cur for select * from employee where id > employeeid order by id;
end proc_resultset;
/
/*
* プロシージャー定義2(SQLブロック)
*/
create or replace procedure proc2
( cur out sys_refcursor,
employeeid in numeric
) as
begin
open cur for select * from employee where id > employeeid order by id;
end proc_resultset;
/
コメントは1行コメント -- とブロックコメント /* */ の2種類が使用できます。 コメントは取り除かれてデータベースへ発行されます。
1つのSQLステートメントは複数行に分けて記述できます。 ステートメントはセミコロン ; で区切らなければいけません。 改行はステートメントの区切りとはみなされません。
ストアドプロシージャーなどのブロックの区切りは、 Dialect のデフォルトの値か、 @Script の blockDelimiter 要素に指定した値を使用して示せます。 この例では、 OracleDialect のデフォルトの区切り文字であるスラッシュ / を使用しています。 ブロックの 区切り文字は行頭に記述し、 区切り文字の後ろには何も記述しないようにしてください。 つまり、区切り文字だけの行としなければいけません。
SQL のログ出力形式¶
@Script の sqlLog 要素に SQL のログ出力形式を指定できます。
@Script(sqlLog = SqlLogType.RAW)
void createTable();
SqlLogType.RAW はバインドパラメータ(?)付きの SQL をログ出力することを表します。
SQLプロセッサ¶
SQLテンプレートで組み立てられたSQLをアプリケーションで扱うには、 @SqlProcessor をDaoのメソッドに注釈します。
@Config(config = AppConfig.class)
public interface EmployeeDao {
@SqlProcessor
<R> R process(Integer id, BiFunction<Config, PreparedSql, R> handler);
...
}
メソッドに対応する SQL が必須です。
警告
SQLプロセッサを使ってSQLを組み立て実行する場合、潜在的には常にSQLインジェクションのリスクがあります。 まずは、他のクエリもしくはクエリビルダを使う方法を検討してください。 また、SQLプロセッサでは信頼できない値をSQLの組み立てに使わないように注意してください。
戻り値¶
メソッドの戻り値は任意の型にできます。 ただし、 BiFunction 型のパラメータの3番目の型パラメータと合わせる必要があります。
なお、戻り値の型を void にする場合、 BiFunction 型のパラメータの3番目の型パラメータには Void を指定します。
@SqlProcessor
void process(Integer id, BiFunction<Config, PreparedSql, Void> handler);
パラメータ¶
BiFunction 型のパラメータを1つのみ含める必要があります。 BiFunction 型のパラメータはSQLテンプレート処理後のSQLを処理するために使われます。
その他のパラメータはSQLテンプレートで参照できます。 基本的には、 検索 の問い合わせ条件に指定できるのと同じ型を使用できます。
利用例¶
例えば、SQLテンプレートで処理したSQLをさらに変形し直接実行することができます。(この例では例外処理を省略しています。)
EmployeeDao dao = ...
dao.process(1, (config, preparedSql) -> {
String sql = preparedSql.getRawSql();
String anotherSql = createAnotherSql(sql);
DataSource dataSource = config.getDataSource()
Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement(anotherSql);
return statement.execute();
});
select * from employee where id = /*^ id */0
クエリビルダ¶
org.seasar.doma.jdbc.builder パッケージでは、 プログラムでSQLを組み立てるためのクエリビルダを提供しています。
何らかの理由により クエリ の利用が難しい場合にのみ、 クエリビルダを利用することを推奨します。 また、クエリビルダは デフォルトメソッド の中で使用することを推奨します。
どのクエリビルダも、インスタンスは Config 型の引数をとる static な newInstance メソッドで生成できます。 インスタンスには、 sql メソッドでSQL文字列の断片を、 param メソッドと literal メソッドでパラメータの型とパラメータを渡せます。
param メソッドで渡されたパラメータは PreparedStatement のバインド変数として扱われます。
literal メソッドで渡されたパラメータはSQLにリテラルとして埋め込まれます。 このメソッドでパラメータが渡された場合、SQLインジェクション対策としてのエスケープ処理は実施されません。 しかし、SQLインジェクションを防ぐためにパラメータの値にシングルクォテーションを含めることは禁止しています。
検索¶
検索には、 SelectBuilder クラスを使用します。
利用例は次のとおりです。
SelectBuilder builder = SelectBuilder.newInstance(config);
builder.sql("select");
builder.sql("id").sql(",");
builder.sql("name").sql(",");
builder.sql("salary");
builder.sql("from Emp");
builder.sql("where");
builder.sql("job_type = ").literal(String.class, "fulltime");
builder.sql("and");
builder.sql("name like ").param(String.class, "S%");
builder.sql("and");
builder.sql("age > ").param(int.class, 20);
Emp emp = builder.getEntitySingleResult(Emp.class);
組み立てたSQLのいくつかの方法で取得できます。
挿入¶
挿入には、 InsertBuilder クラスを使用します。
利用例は次のとおりです。
InsertBuilder builder = InsertBuilder.newInstance(config);
builder.sql("insert into Emp");
builder.sql("(name, salary)");
builder.sql("values (");
builder.param(String.class, "SMITH").sql(", ");
builder.param(BigDecimal.class, new BigDecimal(1000)).sql(")");
builder.execute();
組み立てたSQLは execute メソッドで実行できます。
更新¶
更新には、 UpdateBuilder クラスを使用します。
利用例は次のとおりです。
UpdateBuilder builder = UpdateBuilder.newInstance(config);
builder.sql("update Emp");
builder.sql("set");
builder.sql("name = ").param(String.class, "SMIHT").sql(",");
builder.sql("salary = ").param(BigDecimal.class, new BigDecimal("1000"));
builder.sql("where");
builder.sql("id = ").param(int.class, 10);
builder.execute();
組み立てたSQLは execute メソッドで実行できます。
削除¶
削除には、 DeleteBuilder クラスを使用します。
利用例は次のとおりです。
DeleteBuilder builder = DeleteBuilder.newInstance(config);
builder.sql("delete from Emp");
builder.sql("where");
builder.sql("name = ").param(String.class, "SMITH");
builder.sql("and");
builder.sql("salary = ").param(BigDecimal.class, new BigDecimal(1000));
builder.execute();
組み立てたSQLは execute メソッドで実行できます。
SQL¶
目次
SQL テンプレート¶
SQL は SQL テンプレートを使用して記述します。
SQL テンプレートの文法は SQL のブロックコメント /* */ をベースにしたもので あるため1つのテンプレートは次の2つの方法で使用できます。
- Doma でテンプレートの文法を解釈し動的にSQLを組み立てて実行する
- SQL のツールでテンプレートの文法はコメントアウトされたものとして 静的な SQL を実行する
この特徴は 2-way SQL と呼ばれることがあります。
SQL テンプレートはファイルに記述してDaoのメソッドにマッピングする必要があります。
たとえば、 SQL ファイルには次のような SQL テンプレートを格納します。
select * from employee where employee_id = /* employeeId */99
ここでは、ブロックコメントで囲まれた employeeId がDaoインタフェースのメソッドのパラメータに対応し、 直後の 99 はテスト用のデータになります。 テスト用のデータは、 Doma に解釈されて実行される場合には使用されません。 SQL のツールによる静的な実行時にのみ使用されます。
対応するDaoインタフェースのメソッドは次のとおりです。
Employee selectById(employeeId);
アノテーション¶
SQLファイルとDaoのメソッドのマッピングは次のアノテーションで示します。
- @Select
- @Insert(sqlFile = true)
- @Update(sqlFile = true)
- @Delete(sqlFile = true)
- @BatchInsert(sqlFile = true)
- @BatchUpdate(sqlFile = true)
- @BaatchDelete(sqlFile = true)
SQLファイル¶
ファイル名の形式¶
ファイル名は、次の形式でなければいけません。
META-INF/Daoのクラスの完全修飾名をディレクトリに変換したもの/Daoのメソッド名.sql
例えば、 Daoのクラスが aaa.bbb.EmployeeDao でマッピングしたいメソッドが selectById の場合、パス名は次のようになります。
META-INF/aaa/bbb/EmployeeDao/selectById.sql
複数の RDBMS を使用する環境下で特定の RDBMS では別の SQL ファイルを使いたい場合、 拡張子 .sql の前にハイフン区切りで RDBMS 名を入れることで、 優先的に使用するファイルを指示できます。 たとえば、PostgreSQL専用のSQLファイルは次の名前にします。
META-INF/aaa/bbb/EmployeeDao/selectById-postgres.sql
この例ではPostgreSQLを使用している場合に限り、 META-INF/aaa/bbb/EmployeeDao/selectById.sql よりも META-INF/aaa/bbb/EmployeeDao/selectById-postgres.sql が優先的に使用されます。
RDBMS 名は、 Dialect の getName メソッドの値が使用されます。 あらかじめ用意された Dialect についてそれぞれの RDBMS 名を以下の表に示します。
データベース | Dialect | RDBMS 名 |
---|---|---|
DB2 | Db2Dialect | db2 |
H2 Database Engine 1.2.126 | H212126Dialect | h2 |
H2 Database | H2Dialect | h2 |
HSQLDB | HsqldbDialect | hsqldb |
Microsoft SQL Server 2008 | Mssql2008Dialect | mssql |
Microsoft SQL Server | MssqlDialect | mssql |
MySQL | MySqlDialect | mysql |
Oracle Database | OracleDialect | oracle |
PostgreSQL | PostgresDialect | postgres |
SQLite | SqliteDialect | sqlite |
SQL コメント¶
SQL コメント中に式を記述することで値のバインディングや条件分岐を行います。 Doma に解釈されるSQLコメントを 式コメント と呼びます。
式コメントには以下のものがあります。
ノート
式コメントに記述できる式の文法については 式言語 を参照してください。
バインド変数コメント¶
バインド変数を示す式コメントを バインド変数 コメントと呼びます。 バインド変数は、 java.sql.PreparedStatement を介してSQLに設定されます。
バインド変数は /*~*/ というブロックコメントで囲んで示します。 バインド変数の名前はDaoメソッドのパラメータ名に対応します。 対応するパラメータの型は 基本型 もしくは ドメインクラス でなければいけません。 バインド変数コメントの直後にはテスト用データを指定する必要があります。 ただし、テスト用データは実行時には使用されません。
基本型もしくはドメインクラス型のパラメータ¶
Dao インタフェースのメソッドのパラメータが 基本型 もしくは ドメインクラス の場合、 このパラメータは1つのバインド変数を表現できます。 バインド変数コメントはバインド変数を埋め込みたい場所に記述し、 バインド変数コメントの直後にはテスト用データを指定しなければいけません。 Dao インタフェースのメソッドと対応する SQL の例は次のとおりです。
Employee selectById(Integer employeeId);
select * from employee where employee_id = /* employeeId */99
Iterable型のパラメータ¶
Dao インタフェースのメソッドのパラメータが java.lang.Iterable のサブタイプの場合、 このパラメータは、 IN句内の複数のバインド変数を表現できます。 ただし、 java.lang.Iterable のサブタイプの実型引数は 基本型 もしくは ドメインクラス でなければいけません。 バインド変数コメントはINキーワードの直後に置き、 バインド変数コメントの直後には括弧つきでテスト用データを指定しなければいけません。 Dao インタフェースのメソッドと対応する SQL の例は次のとおりです。
List<Employee> selectByIdList(List<Integer> employeeIdList);
select * from employee where employee_id in /* employeeIdList */(1,2,3)
Iterable が空であるとき、IN句の括弧内の値は null になります。
select * from employee where employee_id in (null)
任意の型のパラメータ¶
Dao インタフェースのメソッドのパラメータが 基本型 もしくは ドメインクラス でない場合、 パラメータは複数のバインド変数コメントに対応します。 バインド変数コメントの中では、ドット . を使用し任意の型のフィールドやメソッドにアクセスできます。 Dao インタフェースのメソッドと対応する SQL の例は次のとおりです。
EmployeeDto クラスには、 employeeName フィールドや salary フィールドが存在するものとします。
List<Employee> selectByNameAndSalary(EmployeeDto dto);
select * from employee
where
employee_name = /* dto.employeeName */'abc'
and
salary = /* dto.salary */1234
フィールドにアクセスする代わりに public なメソッドを呼び出すことも可能です。
select * from employee
where
salary = /* dto.getTaxedSalary() */1234
リテラル変数コメント¶
リテラル変数を示す式コメントを リテラル変数 コメントと呼びます。 リテラル変数は、 SQLのリテラルの形式に変換された後にSQLに埋め込まれます。 リテラルの形式に変換とは、文字列型をシングルクォートで囲むなどを指します。 この変換にはSQLインジェクション対策としてのエスケープ処理は含まれません。
SQL インジェクションを防ぐため、リテラル変数の値にシングルクォテーションを含めることは禁止しています。
リテラル変数は /*^~*/ というブロックコメントで囲んで示します。 リテラル変数の名前はDaoメソッドのパラメータ名に対応します。 対応するパラメータの型は 基本型 もしくは ドメインクラス でなければいけません。 リテラル変数コメントの直後にはテスト用データを指定する必要があります。 ただし、テスト用データは実行時には使用されません。
Dao インタフェースのメソッドと対応する SQL の例は次のとおりです。
Employee selectByCode(String code);
select * from employee where code = /*^ code */'test'
Dao の呼び出し例は次の通りです。
EmployeeDao dao = new EmployeeDaoImpl();
List<Employee> list = dao.selectByCode("abc");
発行される SQL は次のようになります。
select * from employee where code = 'abc'
記法が異なることを除けば、使い方はバインド変数コメントと同様です。
ノート
リテラル変数コメントは、実行計画を固定するなどあえてバインド変数の使用を避けたい場合に利用できます。
埋め込み変数コメント¶
埋め込み変数を示す式コメントを 埋め込み変数 コメントと呼びます。 埋め込み変数の値は SQL を組み立てる際に SQL の一部として直接埋め込まれます。
SQL インジェクションを防ぐため、埋め込み変数の値に以下の値を含めることは禁止しています。
- シングルクォテーション
- セミコロン
- 行コメント
- ブロックコメント
埋め込み変数は /*#~*/ というブロックコメントで示します。 埋め込み変数の名前は Dao メソッドのパラメータ名にマッピングされます。
Dao のメソッドと対応する SQL の例は次のとおりです。
List<Employee> selectAll(BigDecimal salary, String orderyBy);
select * from employee where salary > /* salary */100 /*# orderBy */
Dao の呼び出し例は次の通りです。
EmployeeDao dao = new EmployeeDaoImpl();
BigDecimal salary = new BigDecimal(1000);
String orderBy = "order by salary asc, employee_name";
List<Employee> list = dao.selectAll(salary, orderBy);
発行される SQL は次のようになります。
select * from employee where salary > ? order by salary asc, employee_name
ノート
埋め込み変数コメントは、 ORDER BY 句など SQL の一部をプログラムで組み立てたい場合に使用できます。
条件コメント¶
ifとend¶
条件分岐を示す式コメントを条件コメントと呼びます。 構文は次のとおりです。
/*%if 条件式*/ ~ /*%end*/
条件式は結果が boolean もしくは java.lang.Boolean 型と評価される式でなければいけません。 例を示します。
select * from employee where
/*%if employeeId != null */
employee_id = /* employeeId */99
/*%end*/
上記の SQL 文は employeeId が null でない場合、 次のような準備された文に変換されます。
select * from employee where employee_id = ?
この SQL 文は employeeId が null の場合に次のような準備された文に変換されます。
select * from employee
if の条件が成り立たない場合に if の外にある WHERE句が出力されないのは、 条件コメントにおけるWHEREやHAVINGの自動除去 機能が働いているためです。
条件コメントにおけるWHEREやHAVINGの自動除去¶
条件コメントを使用した場合、条件の前にある WHERE や HAVING について自動で出力の要/不要を判定します。 たとえば、次のようなSQLで employeeId が null の場合、
select * from employee where
/*%if employeeId != null */
employee_id = /* employeeId */99
/*%end*/
/*%if ~*/ の前の where は自動で除去され、次のSQLが生成されます。
select * from employee
条件コメントにおけるANDやORの自動除去¶
条件コメントを使用した場合、条件の後ろにつづく AND や OR について自動で出力の要/不要を判定します。 たとえば、次のようなSQLで employeeId が null の場合、
select * from employee where
/*%if employeeId != null */
employee_id = /* employeeId */99
/*%end*/
and employeeName like 's%'
/*%end*/ の後ろの and は自動で除去され、次の SQL が生成されます。
select * from employee where employeeName like 's%'
elseifとelse¶
/*%if 条件式*/ と /*%end*/ の間では、 elseif や else を表す次の構文も使用できます。
- /*%elseif 条件式*/
- /*%else*/
例を示します。
select
*
from
employee
where
/*%if employeeId != null */
employee_id = /* employeeId */9999
/*%elseif department_id != null */
and
department_id = /* departmentId */99
/*%else*/
and
department_id is null
/*%end*/
上の SQL は、 employeeId != null が成立するとき実際は次の SQL に変換されます。
select
*
from
employee
where
employee_id = ?
employeeId == null && department_id != null が成立するとき、実際は次の SQL に変換されます。 department_id の直前の AND は自動で除去されるため出力されません。
select
*
from
employee
where
department_id = ?
employeeId == null && department_id == null が成立するとき、実際は次の SQL に変換されます。 department_id の直前の AND は自動で除去されるため出力されません。
select
*
from
employee
where
department_id is null
ネストした条件コメント¶
条件コメントはネストさせることができます。
select * from employee where
/*%if employeeId != null */
employee_id = /* employeeId */99
/*%if employeeName != null */
and
employee_name = /* employeeName */'hoge'
/*%else*/
and
employee_name is null
/*%end*/
/*%end*/
条件コメントにおける制約¶
条件コメントの if と end はSQLの同じ節に含まれなければいけません。 節とは、 SELECT節、FROM節、WHERE節、GROUP BY節、HAVING節、ORDER BY節などです。 次の例では、 if がFROM節にあり end がWHERE節にあるため不正です。
select * from employee /*%if employeeId != null */
where employee_id = /* employeeId */99 /*%end*/
また、 if と end は同じレベルの文に含まれなければいけません。 次の例では、 if が括弧の外にありendが括弧の内側にあるので不正です。
select * from employee
where employee_id in /*%if departmentId != null */(... /*%end*/ ...)
繰り返しコメント¶
forとend¶
繰り返しを示す式コメントを繰り返しコメントと呼びます。 構文は次のとおりです。
/*%for 識別子 : 式*/ ~ /*%end*/
識別子は、繰り返される要素を指す変数です。 式は java.lang.Iterable 型として評価される式でなければいけません。 例を示します。
select * from employee where
/*%for name : names */
employee_name like /* name */'hoge'
/*%if name_has_next */
/*# "or" */
/*%end */
/*%end*/
上記の SQL 文は、 names が3つの要素からなるリストを表す場合、次のような準備された文に変換されます。
select * from employee where
employee_name like ?
or
employee_name like ?
or
employee_name like ?
item_has_nextとitem_index¶
/*%for 識別子 : 式*/ から /*%end*/ までの内側では次の2つの特別な変数を使用できます。
- item_has_next
- item_index
接頭辞の item は識別子を表します。つまり、 for の識別子が name の場合 この変数はそれぞれ name_has_next と name_index となります。
item_has_next は次の繰り返し要素が存在するかどうかを示す boolean の値です。
item_index は繰り返しのindexを表す int の値です。値は0始まりです。
繰り返しコメントにおけるWHEREやHAVINGの自動除去¶
繰り返しコメントを使用した場合、コメントの前にある WHERE や HAVING について自動で出力の要/不要を判定します。 たとえば、次のような SQL で names の size が 0 の場合(繰り返しが行われない場合)、
select * from employee where
/*%for name : names */
employee_name like /* name */'hoge'
/*%if name_has_next */
/*# "or" */
/*%end */
/*%end*/
/*%for ~*/ の前の where は自動で除去され、次の SQL が生成されます。
select * from employee
繰り返しコメントにおけるANDやORの自動除去¶
繰り返しコメントを使用した場合、コメントの後ろにつづく AND や OR について自動で出力の要/不要を判定します。 たとえば、次のような SQL で names の size が 0 の場合(繰り返しが行われない場合)、
select * from employee where
/*%for name : names */
employee_name like /* name */'hoge'
/*%if name_has_next */
/*# "or" */
/*%end */
/*%end*/
or
salary > 1000
/*%end*/ の後ろの or は自動で除去され、次のSQLが生成されます。
select * from employee where salary > 1000
繰り返しコメントにおける制約¶
繰り返しコメントの for と end は SQL の同じ節に含まれなければいけません。 節とは、SELECT節、FROM節、WHERE節、GROUP BY節、HAVING節、ORDER BY節などです。
また、 for と end は同じレベルの文に含まれなければいけません。 つまり、括弧の外で for 、括弧の内側で end という記述は認められません。
選択カラムリスト展開コメント¶
expand¶
SELECT節のアスタリスク * を エンティティクラス の定義を 参照して自動でカラムのリストに展開する式を選択カラムリスト展開コメントと呼びます。 構文は次のとおりです。
/*%expand エイリアス*/
エイリアスは文字列として評価される式でなければいけません。 エイリアスは省略可能です。
このコメントの直後にはアスタリスク * が必須です。
例を示します。
select /*%expand*/* from employee
上記のSQL文の結果が次のような エンティティクラス にマッピングされているものとします。
@Entity
public class Employee {
Integer id;
String name;
Integer age;
}
このとき、 SQL は以下のように変換されます。
select id, name, age from employee
SQL 上でテーブルにエイリアスを指定する場合、 選択カラムリスト展開コメントにも同じエイリアスを指定してください。
select /*%expand "e" */* from employee e
このとき、 SQL は以下のように変換されます。
select e.id, e.name, e.age from employee e
更新カラムリスト生成コメント¶
populate¶
UPDATE文のSET節 を エンティティクラス の定義を 参照して自動で生成する式を更新カラムリスト生成コメントと呼びます。 構文は次のとおりです。
/*%populate*/
例を示します。
update employee set /*%populate*/ id = id where age < 30
上記のSQL文への入力が次のような エンティティクラス にマッピングされているものとします。
@Entity
public class Employee {
Integer id;
String name;
Integer age;
}
このとき、 SQL は以下のように変換されます。
update employee set id = ?, name = ?, age = ? where age < 30
更新カラムリスト生成コメントは、 /*%populate*/ からWHERE句までをカラムリストで置き換えます。 つまり、元のSQLにあった id = id の記述は最終的なSQLからは削除されます。
通常のブロックコメント¶
/* の直後に続く3文字目がJavaの識別子の先頭で使用できない文字 (ただし、空白および式で特別な意味をもつ %、#、 @、 "、 ' は除く)の場合、 それは通常のブロックコメントだとみなされます。
たとえば、次の例はすべて通常のブロックコメントです。
/**~*/
/*+~*/
/*=~*/
/*:~*/
/*;~*/
/*(~*/
/*)~*/
/*&~*/
一方、次の例はすべて式コメントだとみなされます。
/* ~*/ ...--3文字目が空白であるため式コメントです。
/*a~*/ ...--3文字目がJavaの識別子の先頭で使用可能な文字であるため式コメントです。
/*$~*/ ...--3文字目がJavaの識別子の先頭で使用可能な文字であるため式コメントです。
/*%~*/ ...--3文字目が条件コメントや繰り返しコメントの始まりを表す「%」であるため式コメントです。
/*#~*/ ...--3文字目が埋め込み変数コメントを表す「#」であるため式コメントです。
/*@~*/ ...--3文字目が組み込み関数もしくはクラス名を表す「@」であるため式コメントです。
/*"~*/ ...--3文字目が文字列リテラルの引用符を表す「"」であるため式コメントです。
/*'~*/ ...--3文字目が文字リテラルの引用符を表す「'」であるため式コメントです。
ノート
特に理由がない場合、通常のブロックコメントには 最初のアスタリスクを2つ重ねる /**~*/ を使用するのがよいでしょう。
式言語¶
目次
SQL 中の式コメントには簡易な式を記述できます。 文法はJavaとほとんど同じです。 ただし、Javaで可能なことすべてができるわけではありません。
ノート
特に大きな違いは、 java.util.Optional などのオプショナルな型の扱い方にあります。 式の中では、 Optional 型の値は常にその要素の型の値に自動変換されます。 たとえば、 Optional<String> 型の値は String 型の値として扱われます。 したがって、 Optional 型のメソッドを呼び出したり、 Optional 型をパラメータとするメソッドの呼び出しはできません。
値の存在の有無を確認する場合は、 /*%if optional.isPresent() */ とする替わりに /*%if optional != null */ としてください。
java.util.OptionalInt 、 java.util.OptionalDouble 、 java.util.OptionalLong についても同様です。
リテラル¶
以下のリテラルが用意されています。
リテラル | 型 |
---|---|
null | void |
true | boolean |
false | boolean |
10 | int |
10L | long |
0.123F | float |
0.123D | double |
0.123B | java.math.BigDecimal |
‘a’ | char |
“a” | java.lang.String |
数値の型は、リテラルの最後に L や F などのサフィックスを付与して区別します。 サフィックスは大文字でなければいけません。
select * from employee where
/*%if employeeName != null && employeeName.length() > 10 */
employee_name = /* employeeName */'smith'
/*%end*/
比較演算子¶
以下の比較演算子を使用できます。
演算子 | 説明 |
---|---|
== | 等値演算子 |
!= | 不等値演算子 |
< | 小なり演算子 |
<= | 小なりイコール演算子 |
> | 大なり演算子 |
>= | 大なりイコール演算子 |
比較演算子を利用するには、 被演算子が java.lang.Comparable を実装している必要があります。
< 、 <= 、 > 、 >= では、被演算子が null であってはいけません。
select * from employee where
/*%if employeeName.indexOf("s") > -1 */
employee_name = /* employeeName */'smith'
/*%end*/
論理演算子¶
以下の論理演算子を使用できます。
演算子 | 説明 |
---|---|
! | 論理否定演算子 |
&& | 論理積演算子 |
|| | 論理和演算子 |
括弧を使って、演算子が適用される優先度を制御できます。
select * from employee where
/*%if (departmentId == null || managerId == null) and employee_name != null */
employee_name = /* employeeName */'smith'
/*%end*/
算術演算子¶
以下の算術演算子を使用できます。
演算子 | 説明 |
---|---|
+ | 加算演算子 |
- | 減算演算子 |
* | 乗算演算子 |
/ | 除算演算子 |
% | 剰余演算子 |
被演算子は数値型でなければいけません。
select * from employee where
salary = /* salary + 1000 */0
連結演算子¶
連結演算子 + を使って文字を連結できます。
被演算子は次のいずれかの型でなければいけません。
- java.lang.String
- java.lang.Character
- char
select * from employee where
employee_name like /* employeeName + "_" */'smith'
インスタンスメソッドの呼び出し¶
ドット . で区切ってメソッド名を指定することでインスタンスメソッドを実行可能です。 実行可能なメソッドは可視性がpublicなものだけに限られます。
select * from employee where
/*%if employeeName.startsWith("s") */
employee_name = /* employeeName */'smith'
/*%end*/
引数がない場合はメソッド名の後ろに () を指定します。
select * from employee where
/*%if employeeName.length() > 10 */
employee_name = /* employeeName */'smith'
/*%end*/
インスタンスフィールドへのアクセス¶
ドット . で区切ってフィールド名を指定することでインスタンスフィールドにアクセスできます。 可視性はprivateであってもアクセス可能です。
select * from employee where
employee_name = /* employee.employeeName */'smith'
staticメソッドの呼び出し¶
@ で囲まれたクラスの完全修飾名にメソッドを続けることでstaticメソッドを実行可能です。 実行可能なメソッドは可視性がpublicなものだけに限られます。
select * from employee where
/*%if @java.util.regex.Pattern@matches("^[a-z]*$", employeeName) */
employee_name = /* employeeName */'smith'
/*%end*/
staticフィールドへのアクセス¶
@ で囲まれたクラスの完全修飾名にフィールドを続けることでstaticフィールドにアクセスできます。 可視性はprivateであってもアクセス可能です。
select * from employee where
/*%if employeeName.length() < @java.lang.Byte@MAX_VALUE */
employee_name = /* employeeName */'smith'
/*%end*/
組み込み関数の使用¶
組み込み関数は、主に、SQLにバインドする前にバインド変数の値を変更するためのユーティリティです。
たとえば、LIKE句で前方一致検索を行う場合に次のように記述できます。
select * from employee where
employee_name like /* @prefix(employee.employeeName) */'smith' escape '$'
ここでは、 @prefix(employee.employeeName) というように、 employee.employeeName を @prefix 関数に渡しています。 @prefix 関数は、パラメータで受け取る文字列を前方一致検索用の文字列に変換します。 また、特別な意味を持つ文字をエスケープします。 たとえば employee.employeeName の値が ABC である場合、 値は ABC% に変換されます。 もし、 employee.employeeName の値が AB%C というように % を含んでいる場合、 % はデフォルトのエスケープシーケンス $ でエスケープされ、値は AB$%C% に変換されます。
使用可能な関数のシグネチャは以下のとおりです。
- String @escape(String text, char escapeChar = ‘$’)
- LIKE演算のためのエスケープを行うことを示します。 戻り値は入力値をエスケープした文字列です。 escapeChar が指定されない場合、デフォルトのエスケープ文字 $ が使用されます。 引数に null を渡した場合、 null を返します。
- String @prefix(String prefix, char escapeChar = ‘$’)
- 前方一致検索を行うことを示します。 戻り値は入力値をエスケープしワイルドカードを後ろに付与した文字列です。 escapeChar が指定されない場合、デフォルトのエスケープ文字 $ が使用されます。 引数に null を渡した場合、 null を返します。
- String @infix(String infix, char escapeChar = ‘$’)
- 中間一致検索を行うことを示します。 戻り値は入力値をエスケープしワイルドカードを前と後ろに付与した文字列です。 escapeChar が指定されない場合、デフォルトのエスケープ文字 $ が使用されます。 引数に null を渡した場合、 null を返します。
- String @suffix(String suffix, char escapeChar = ‘$’)
- 後方一致検索を行うことを示します。 戻り値は入力値をエスケープしワイルドカードを前に付与した文字列です。 escapeChar が指定されない場合、デフォルトのエスケープ文字 $ が使用されます。 引数に null を渡した場合、 null を返します。
- java.util.Date @roundDownTimePart(java.util.Date date)
- 時刻部分を切り捨てることを示します。 戻り値は時刻部分が切り捨てられた新しい日付です。 引数に null を渡した場合、 null を返します。
- java.sql.Date @roundDownTimePart(java.sql.Date date)
- 時刻部分を切り捨てることを示します。 戻り値は時刻部分が切り捨てられた新しい日付です。 引数に null を渡した場合、 null を返します。
- java.sql.Timestamp @roundDownTimePart(java.sql.Timestamp timestamp)
- 時刻部分を切り捨てることを示します。 戻り値は時刻部分が切り捨てられた新しいタイムスタンプです。 引数に null を渡した場合、 null を返します。
- java.util.Date @roundUpTimePart(java.util.Date date)
- 時刻部分を切り上げることを示します。 戻り値は時刻部分が切り上げられた新しい日付です。 引数に null を渡した場合、 null を返します。
- java.sql.Date @roundUpTimePart(java.sql.Date date)
- 時刻部分を切り上げることを示します。 戻り値は時刻部分が切り上げられた新しい日付です。 引数に null を渡した場合、 null を返します。
- java.sql.Timestamp @roundUpTimePart(java.sql.Timestamp timestamp)
- 時刻部分を切り上げることを示します。 戻り値は時刻部分が切り上げられた新しいタイムスタンプです。 引数に null を渡した場合、 null を返します。
- boolean @isEmpty(CharSequence charSequence)
- 文字シーケンスが null 、もしくは文字シーケンスの長さが 0 の場合 true を返します。
- boolean @isNotEmpty(CharSequence charSequence)
- 文字シーケンスが null でない、かつ文字シーケンスの長さが 0 でない場合 true を返します。
- boolean @isBlank(CharSequence charSequence)
- 文字シーケンスが null 、もしくは文字シーケンスの長さが 0 、 もしくは文字シーケンスが空白だけから形成される場合 trueを返します。
- boolean @isNotBlank(CharSequence charSequence)
- 文字シーケンスが null でない、かつ文字シーケンスの長さが 0 でない、 かつ文字シーケンスが空白だけで形成されない場合 true を返します。
これらの関数は、 org.seasar.doma.expr.ExpressionFunctions のメソッドに対応しています。
カスタム関数の使用¶
関数を独自に定義し使用できます。
独自に定義した関数(カスタム関数)を使用するには次の設定が必要です。
- 関数は、 org.seasar.doma.expr.ExpressionFunctions を実装したクラスのメソッドとして定義する。
- メソッドはpublicなインスタンスメソッドとする。
- 作成したクラスは 注釈処理 のオプションで登録する。 オプションのキーは doma.expr.functions である。
- 作成したクラスのインスタンスを設定クラスのRDBMSの方言で使用する (Domaが提供するRDBMSの方言の実装はコンストラクタで ExpressionFunctions を受け取ることが可能)。
カスタム関数を呼び出すには、組み込み関数と同じように関数名の先頭に @ をつけます。 たとえば、 myfunc という関数の呼び出しは次のように記述できます。
select * from employee where
employee_name = /* @myfunc(employee.employeeName) */'smith'
トランザクション¶
Domaは、ローカルトランザクションをサポートします。 このドキュメントでは、ローカルトランザクションの設定方法と利用方法について説明します。
グローバルトランザクションを使用したい場合は、JTA(Java Transaction API) の実装をもつフレームワークやアプリケーションサーバーの機能を利用してください。
設定¶
ローカルトランザクションを実行するには次の条件を満たす必要があります。
- Config の getDataSource で LocalTransactionDataSource を返す
- 上記の LocalTransactionDataSource をコンストラクタで受けて LocalTransactionManager を生成する
- 上記の LocalTransactionManager の管理下でデータベースアクセスを行う
LocalTransactionManager の生成と取得方法はいくつかありますが、最も単純な方法は、 Config の実装クラスのコンストラクタで生成し Config の実装クラスをシングルトンとすることです。
実装例です。
@SingletonConfig
public class AppConfig implements Config {
private static final AppConfig CONFIG = new AppConfig();
private final Dialect dialect;
private final LocalTransactionDataSource dataSource;
private final TransactionManager transactionManager;
private AppConfig() {
dialect = new H2Dialect();
dataSource = new LocalTransactionDataSource(
"jdbc:h2:mem:tutorial;DB_CLOSE_DELAY=-1", "sa", null);
transactionManager = new LocalTransactionManager(
dataSource.getLocalTransaction(getJdbcLogger()));
}
@Override
public Dialect getDialect() {
return dialect;
}
@Override
public DataSource getDataSource() {
return dataSource;
}
@Override
public TransactionManager getTransactionManager() {
return transactionManager;
}
public static AppConfig singleton() {
return CONFIG;
}
}
ノート
クラスに @SingletonConfig を指定することでシングルトンであることを表しています
利用例¶
設定 で示した AppConfig クラスを以下のようにDaoインタフェースに注釈するものとして例を示します。
@Dao(config = AppConfig.class)
public interface EmployeeDao {
...
}
以降のコード例に登場する dao は上記クラスのインスタンスです。
トランザクションの開始と終了¶
トランザクションは TransactionManager の以下のメソッドのいずれかを使って開始します。
- required
- requiresNew
- notSupported
トランザクション内で行う処理はラムダ式として渡します。
TransactionManager tm = AppConfig.singleton().getTransactionManager();
tm.required(() -> {
Employee employee = dao.selectById(1);
employee.setName("hoge");
employee.setJobType(JobType.PRESIDENT);
dao.update(employee);
});
ラムダ式が正常に終了すればトランザクションはコミットされます。 ラムダ式が例外をスローした場合はトランザクションはロールバックされます。
明示的なロールバック¶
例外をスローする方法以外でトランザクションをロールバックするには setRollbackOnly メソッドを呼び出します。
TransactionManager tm = AppConfig.singleton().getTransactionManager();
tm.required(() -> {
Employee employee = dao.selectById(1);
employee.setName("hoge");
employee.setJobType(JobType.PRESIDENT);
dao.update(employee);
// ロールバックするものとしてマークする
tm.setRollbackOnly();
});
セーブポイント¶
セーブポイントを使用することで、トランザクション中の特定の変更を取り消すことができます。
TransactionManager tm = AppConfig.singleton().getTransactionManager();
tm.required(() -> {
// 検索して更新
Employee employee = dao.selectById(1);
employee.setName("hoge");
dao.update(employee);
// セーブポイントを作成
tm.setSavepoint("beforeDelete");
// 削除
dao.delete(employee);
// セーブポイントへ戻る(上で行った削除を取り消す)
tm.rollback("beforeDelete");
});
注釈処理¶
Pluggable Annotation Processing API を利用すると、ソースコードの自動生成や検証を コンパイル時 に行うことができます。
DomaではこのAPIを利用することで @Entity や @Dao と言ったアノテーションが注釈されたクラスやインタフェースを処理し、 必要なクラスを自動生成します。 また、注釈されたクラスやインタフェースの検証を行い、Domaの規約に従っていないソースコードがある場合は エラーメッセージをIDE(Eclipseなど)のエディタやjavacを実行したコンソール上に表示します。
ここでは、Domaが提供するオプションの種類と、ビルドツールごとのオプションの設定方法を説明します。
オプション¶
- doma.dao.package
- @Dao が注釈されたインタフェースの実装クラスが生成されるパッケージ。 何らかの値を指定した場合、doma.dao.subpackageの指定よりも優先される。 デフォルトの値は、 @Dao が注釈されたインタフェースと同じパッケージ。
- doma.dao.subpackage
- @Dao が注釈されたインタフェースの実装クラスが生成されるサブパッケージ。 doma.dao.packageに値を指定していない場合にのみ有効。 @Dao が注釈されたインタフェースのパッケージが example.dao で、ここに指定した値が impl の場合、 生成されるクラスのパッケージは example.dao.impl となる。
- doma.dao.suffix
- @Dao が注釈されたインタフェースの実装クラスの名前のサフィックス。 @Dao が注釈されたインタフェースの単純名が EmployeeDao で、ここに指定した値が Bean の場合、 生成されるクラスの単純名は EmployeeDaoBean となる。 デフォルトの値は Impl 。
- doma.debug
- 注釈処理のデバッグ情報をログ出力するかどうか。 true の場合、ログ出力を行う。 デフォルトの値は、 false 。
- doma.domain.converters
- 任意の型と基本型を相互変換する DomainConverter のプロバイダとなるクラスの完全修飾名のカンマ区切り。 クラスは org.seasar.doma.DomainConverters によって注釈されていないければいけない。
- doma.entity.field.prefix
- @Entity が注釈されたクラスごとに生成されるタイプクラスで使用される。 タイプクラスのpublicなフィールド名のプレフィックス。 none を指定するとプレフィックスを使用しないという意味になる。 デフォルトの値は、 $ 。
- doma.expr.functions
- 式コメントで利用可能な関数群を表すクラスの完全修飾名。 org.seasar.doma.expr.ExpressionFunctions のサブタイプでなければいけない。 デフォルトの値は、 org.seasar.doma.expr.ExpressionFunctions 。
- doma.sql.validation
- SQLファイルの存在チェックとSQLコメントの文法チェックを行う場合は true 。 行わない場合は false 。 デフォルトの値は、 true 。
- doma.version.validation
- 注釈処理によるソースコード生成で利用したDomaのバージョンと実行時のDomaのバージョンが同じであることを チェックする場合は true 。 しない場合は false 。 Domaのあるバージョンで生成されたコードを含むライブラリを作成する場合に false を指定してビルドすると、 そのライブラリの再利用性が高まります。 ライブラリが依存するDomaのバージョンとは異なるバージョンのDomaで実行できるからです (Domaのバージョンに互換性がある限りにおいて)。 デフォルトの値は、 true 。
ビルド¶
Maven Central Repository¶
Doma の jar ファイルは Maven Central Repository から入手できます。 GroupId と ArtifactId の名称は以下の通りです。
GroupId: | org.seasar.doma |
---|---|
ArtifactId: | doma |
Eclipse を使ったビルド¶
Eclipse でビルドを行う際のポイントは以下の通りです。
- プロジェクトの設定で注釈処理を有効にする
- Build Path に加えて Factory Path にも Doma の jar ファイルを設定する
ノート
手動で設定するよりも Gradle の eclipse タスクで自動設定することを推奨します。 詳細については、 domaframework/simple-boilerplate に含まれる build.gradle と eclipse.gradle を参照ください。
注釈処理の有効化¶
注釈処理を有効化するには、メニューから Project > Properties を選んで画面を開き 左のメユーから Java Compiler > Annotation Processing を選択します。
そして、下記に示すチェックボックスにチェックを入れます。

Factory Path の設定¶
注釈処理を有効化するには、メニューから Project > Properties を選んで画面を開き 左のメユーから Java Compiler > Annotation Processing > Factory Path を選択します。
そして、下記に示すチェックボックスにチェックを入れ、 ビルドパスで指定しているのと同じバージョンの Doma の jar を登録します。

Gradle を使ったビルド¶
Gradle でビルドを行う際のポイントは以下のとおりです。
- JavaクラスとSQLファイルの出力先ディレクトリを同じにする
- コンパイルより前にSQLファイルを出力先ディレクトリにコピーする
- 依存関係の設定でdomaへの依存を指定する
サンプルのbuild.gradleです。
apply plugin: 'java'
// JavaクラスとSQLファイルの出力先ディレクトリを同じにする
processResources.destinationDir = compileJava.destinationDir
// コンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する
compileJava.dependsOn processResources
repositories {
mavenCentral()
maven {url 'https://oss.sonatype.org/content/repositories/snapshots/'}
}
dependencies {
compile "org.seasar.doma:doma:2.14.0"
}
ノート
リポジトリにおける https://oss.sonatype.org/content/repositories/snapshots/ の設定は Doma の SNAPSHOT を参照したい場合にのみ必要です。
Doma の SNAPSHOT は Travis-CI でビルドが成功されるたびに作成されリポジトリに配置されます。
Gradle を使ったより詳細なビルドスクリプトの例として、 domaframework/simple-boilerplate を参照にしてください。
Lombok サポート¶
Doma は Lombok のバージョン 1.16.12 以上をサポートしています。
ノート
IDE として Eclipse を利用する場合はバージョン 4.5 以上を使ってください。 4.5 未満ではアノテーションプロッサで取得されるクラスに定義されたフィールドなどの並びがソースコードに記載されている順序と異なり、 正しく動作しないためです。
Lombok サポートの概要¶
Lombok と Doma は共に JSR 269 で規定されたアノテーションプロセッサを提供していますが、 Lombok と Doma を同時に利用する場合はアノテーションプロセッサの処理順序が問題になることがあります。 例えば、 Lombok のアノテーションプロセッサがコンストラクタを生成し、 Doma のアノテーションプロセッサがそのコンストラクタを読み取る場合などです。 仮に、Doma のアノテーションプロセッサが先に実行されると単にコンストラクタが定義されていないものとして動作し、 コンパイルに失敗します。
アノテーションプロセッサの処理順序を指定できればこの問題は解決するのですが、 残念ながら処理順序が保証されないことが仕様に記載されており、実際に問題解決の仕組みは提供されていません。
Doma では、この問題に対応するために、Lombok のアノテーションの有無を認識して処理順序に依存にしない振る舞いをするようにしています。 先ほどの例で言えば、 @lombok.Value などコンストラクタを生成するLombokのアノテーションが存在する場合は 実際にはコンストラクタを読み取らなくてもコンストラクタが存在するものとして動作するということです。
Lombok 利用のベストプラクティス¶
Lombok のアノテーションを用いたクラスの定義について推奨する方法を記載します。
エンティティクラス¶
immutable(イミュータブル)¶
イミュータブルなエンティティクラスを定義する場合は下記の点に注意します。
- @Entity の immutable 要素に true を設定する
- @lombok.Value もしくは @lombok.AllArgsConstructor を注釈する
- @lombok.AllArgsConstructor を選んだ場合、getterを生成するためには @lombok.Getter を注釈する
@Entity(immutable = true)
@Value
public class Employee {
@Id
Integer id;
String name;
Age age;
}
@Entity(immutable = true)
@AllArgsConstructor
@Getter
public class Worker {
@Id
private final Integer id;
private final String name;
private final Age age;
}
mutable(ミュータブル)¶
ミュータブルなエンティティクラスを定義する場合は下記の点に注意します。
- デフォルトコンストラクタを定義する(デフォルトコンストラクタの生成を抑制しない)
- getter/setterを生成する場合は @lombok.Data もしくは @lombok.Getter / @lombok.Setter を注釈する
@Entity
@Data
public class Person {
@Id
private Integer id;
private String name;
private Age age;
}
@Entity
@Getter
@Setter
public class Businessman {
@Id
private Integer id;
private String name;
private Age age;
}
ドメインクラス¶
ドメインクラスを定義する場合は下記の点に注意します。
- @lombok.Value を注釈する
- インスタンスフィールドは1つだけ定義し名前は value にする
@Domain(valueType = Integer.class)
@Value
public class Age {
Integer value;
}
エンベッダブルクラス¶
エンベッダブルクラスを定義する場合は下記の点に注意します。
- @lombok.Value もしくは @lombok.AllArgsConstructor を注釈する
- @lombok.AllArgsConstructor を選んだ場合、getterを生成するためには @lombok.Getter を注釈する
@Embeddable
@Value
public class Address {
String street;
String city;
}
@Embeddable
@AllArgsConstructor
@Getter
public class Location {
private final String street;
private final String city;
}
Kotlin サポート¶
目次
Doma は Kotlin 1.0.6を実験的にサポートしています。
Kotlin利用のベストプラクティス¶
クラスの定義やビルドに関する事柄について推奨する方法を記載します。
エンティティクラス¶
- Data Classで定義する
- イミュータブルで定義する( @Entity の immutable 要素に true を設定する)
- コンストラクタは1つだけ定義する
- コンストラクタ以外でプロパティを定義しない
- コンストラクタで定義するプロパティには val を使用する
@Entity(immutable = true)
data class Person(
@Id
@GeneratedValue(strategy = org.seasar.doma.GenerationType.IDENTITY)
val id: Int? = null,
val name: Name,
val address: Address)
ドメインクラス¶
- Data Classで定義する
- コンストラクタは1つだけ定義する
- コンストラクタで定義するプロパティの名前は value にする
- コンストラクタで定義するプロパティには val を使用する
@Domain(valueType = String::class)
data class Name(val value: String)
エンベッダブルクラス¶
- Data Classで定義する
- コンストラクタは1つだけ定義する
- コンストラクタ以外でプロパティを定義しない
- コンストラクタで定義するプロパティには val を使用する
@Embeddable
data class Address(val city: String, val street: String)
Daoインタフェース¶
- 更新処理の戻り値の型は org.seasar.doma.jdbc.Result や org.seasar.doma.jdbc.BatchResult を使う
@Dao(config = AppConfig::class)
interface PersonDao {
@Select
fun selectById(id: Int): Person
@Insert
fun insert(person: Person): Result<Person>
}
- 更新処理の戻り値を扱う際は Destructuring Declarations を使う
val dao: PersonDao = ...
val person = Person(name = Name("Jhon"), address = Address(city = "Tokyo", street = "Yaesu"))
val (newPerson, count) = dao.insert(person)
kaptによるビルド¶
Kotlinで記述されたクラスやインタフェースに対して注釈処理をするには kapt を実行する必要があります。 kaptは実験的な位置付けにありドキュメントがありません。 Gradleでビルドする際は、確実な注釈処理が行われるように常に clean build を実行することを推奨します。
./gradlew clean build
Eclispeを利用する場合設定を適切に行えばJavaの注釈処理は自動で行われますが、kapt(Kotlinの注釈処理)はGradleを実行しない限り行われないことに注意してください。
JavaとKotlinの混在¶
kaptの不確実な挙動を避けるため、Domaに関するコードの全てもしくは一部をJavaで書くことは検討に値します。 Domaの利用において、JavaとKotlinの混在は問題ありません。
Developer Documentation¶
Doma の開発¶
目次
ノート
このページは Doma の開発者に向けて書かれています。
ビルド¶
$ ./gradlew build
Maven ローカルリポジトリへのインストール¶
$ ./gradlew build install
ノート
ローカルで修正を加えたコードに対して 統合テスト を実行するには、 ローカルの Maven リポジトリに Doma 本体の成果物を事前にインストールしておく必要があります。
ドキュメント¶
ドキュメントの作成には Sphinx を利用しています。
LiveReload¶
Google Chrome に LiveReload をインストールすることで ドキュメントの修正を即座にブラウザで確認できます。
この拡張機能を有効にした上でサーバーを起動します。
$ python server.py
次の URL でドキュメントを確認できます。
統合テスト¶
ノート
このページは Doma の開発者に向けて書かれています。
統合テストの目的は2つです。
- Eclipse や javac の注釈処理の挙動をテストする
- データベースへのアクセスを伴う機能をテストする
統合テストでは RDBMS を使ったテストを行います。 デフォルトで使用する RDBMS は H2 Database Engine です。
Continuous Integration¶
Travis CI を利用しています。
以下の RDBMS を利用したテストを実行しています。
- H2 Database Engine
- HSQLDB
- MySQL
- PostgreSQL
About Doma¶
リリースノート¶
v2.14.0: 2017-01-14¶
- GH183 BatchUpdateExecutor, BatchDeleteExecutor, MapBatchInsertBuilder を追加
- GH182 エンベッダブルクラスにプロパティを定義しない場合に生成されるコードがコンパイルエラーになっていたのを修正
- GH181 SQLテンプレートで組み立てられたSQLを扱うための @SqlProcessor を追加
- GH180 Lombok をサポート
- GH179 StandardExpressionFunctions#escapeがescapeCharを使用していない
- GH177 Kotlin 1.0.6対応
- GH176 BatchInsertExecutorを追加
- GH175 組み込み関数の LocalDate, LocalDateTime 対応
- GH174 Mapをパラメータとして自動でInsert文を組み立てるMapInsertBuilderを追加
v2.12.0: 2016-07-14¶
v2.11.0: 2016-06-18¶
v2.10.0: 2016-05-28¶
- GH146 Embeddable なオブジェクトが null の場合に更新系の処理が失敗する不具合を修正
- GH145 Kotlin のサポートについてドキュメントを追加
- GH142 エンベッダブルクラスのドキュメントを追加
- GH141 エンティティクラスが継承をしている場合の親プロパティへのアクセス方法を簡易化
- GH140 プリミティブ型のプロパティにnullがアサインされる場合に例外が発生していた不具合をデフォルト値が設定されるように修正
- GH139 @Embeddable をサポート
- GH138 Kotlin でイミュータブルなエンティティを定義する際 @ParameterName を使用する必要性を除去
v2.9.0: 2016-05-16¶
v2.7.0: 2016-02-27¶
v2.6.2: 2016-02-11¶
v2.6.0: 2015-11-21¶
v2.4.0: 2015-08-14¶
- GH93 JdbcMappingHint#getDomainClass() がドメインクラスを返さない問題を修正
- GH89 PortableObjectTypeをジェネリクスにして、String等をvalueTypeとして指定できるように
- GH88 JdbcLoggerのメソッドのtypoを修正。 Failuer -> Failure
- GH87 StandardExpressionFunctionsのサブクラスにpublicなコンストラクタを追加
- GH86 Version number spec is different from the document
- GH84 populate を使ったメソッドで DOMA4122 が出る問題を修正
- GH81 リソースバンドルが取得できない場合はデフォルトのリソースバンドルにフォールバックする
v2.2.0: 2015-03-28¶
- GH71 インターフェースにも@Domainで注釈できるようにしました
- GH70 EntityListenerの取得はEntityListenerProviderを介するようにしました
- GH67 SQL Server の OPTION 句が存在するとページングが正しく実行されない問題を修正しました
- GH66 ネーミング規約の適用をコンパイル時から実行時に変更
- GH64 イミュータブルなエンティティの取得でNullPointerException が発生するバグを修正しました
- GH61 SQL Server 2012 から追加された OFFSET-FETCH をページング処理に使う
- GH60 Mssql2008Dialect の getName() が返す値を変更しました
- GH59 Windows環境でテストが失敗する問題を修正
- GH58 StringUtilのfromCamelCaseToSnakeCaseで、カラム名に数字が含まれている場合意図している結果にならない
v2.1.0: 2014-12-30¶
v2.0.1: 2014-08-06¶
- DomainConverter の第2型引数に byte[] を指定すると注釈処理でコンパイル エラーになる問題を修正しました
v2.0.0: 2014-07-02¶
- UnitOfWork を削除しました
v2.0-beta-5: 2014-06-07¶
- List<Optional<Emp>> や List<Optional<Map<String, Object>>> を戻り値とする Dao メソッドは注釈処理でコンパイルエラーにしました
- Entity 更新後に OriginalStates へ変更が反映されない問題を修正しました
- エンティティの識別子の値がすでに設定されている場合は自動生成処理を実行しないようにしました
- カラムリスト展開コメント で DOMA4257 エラーになる問題を修正しました
- SQLのログ出力方法をアノテーションで制御できるようにしました
- Dao から出力されるログのメッセージを詳細化しました
- UtilLoggingJdbcLogger のロガーの名前をクラスの完全修飾名に変更しました
- SQL実行時にSQLファイルのパスがログに出力されない問題を修正しました
v2.0-beta-4: 2014-05-04¶
- Pluggable Annotation Processing API の Visitor を Java 8 用のものへバージョンアップしました
- 空の java.util.Iterable を IN 句にバインドする場合は SQL の null として扱うようにしました
- java.sql.SQLXML に対応しました
- LocalTransaction で指定したセーブポイント「以降」を削除すべき箇所で「以前」を削除している不具合を修正しました
- LocalTransaction でセーブポイント削除時のログが間違っている不具合を修正しました
- Entity のプロパティの型を byte 配列にすると注釈処理に失敗する不具合を修正しました
v2.0-beta-3: 2014-04-03¶
- 検索結果を java.util.stream.Collector で処理できるようにしました。
- LocalTransactionManager から TransactionManager インタフェースを抽出しました。
- Config で指定した設定が一部無視される不具合を修正しました。
- マップのネーミング規約を一律制御するためのインタフェース MapKeyNaming を追加しました。
- java.time.LocalDate 、 java.time.LocalTime 、 java.time.LocalDateTime を基本型として使用できるようにしました。
- JdbcLogger の実装の差し替えを容易にするために AbstractJdbcLogger を追加しました。
- SelectStrategyType の名前を SelectType に変更しました。
Doma 2 における主要な変更点¶
Doma 1.36.0 からの変更点の内、主なものを示しています。
設定¶
- Config の実装クラスをシングルトンとして扱えるように @SingletonConfig が追加されました。
- トランザクション処理を簡易化するために TransactionManager が追加されました。
- クエリをカスタマイズ可能にするために QueryImplementors と CommandImplementors が追加されました。
- 結果セットに未知のカラムが含まれていた場合の挙動をカスタマイズ可能にするために UnknownColumnHandler が追加されました。
- マップのキーのネーミング規約を一律で解決できるように MapKeyNaming が追加されました。
- DomaAbstractConfig が削除されました。 このクラスが提供していた実装は Config のデフォルトメソッドで提供されるようになりました。
- DefaultClassHelper が削除されました。 このクラスが提供していた実装は ClassHelper のデフォルトメソッドで提供されるようになりました。
- NullRequiresNewController が削除されました。 このクラスが提供していた実装は RequiresNewController のデフォルトメソッドで提供されるようになりました。
基本型¶
- Object 型が基本型と認識されるようになりました。
- java.time.LocalDate 型が基本型と認識されるようになりました。
- java.time.LocalTime 型が基本型と認識されるようになりました。
- java.time.LocalDateTime 型が基本型と認識されるようになりました。
ドメインクラス¶
- @Domain を使ってドメインクラスを作った場合、デフォルトでは null を受け入れなくなりました。 null を受け入れるには @Domain の acceptNull 要素に true の指定が必要です。
エンティティクラス¶
- 実験的な扱いだったイミュータブルなエンティティが正式にサポートされました。
- プロパティに基本型やドメインクラスを要素とする java.util.Optional を定義できるようになりました。
- プロパティに java.util.OptionalInt を定義できるようになりました。
- プロパティに java.util.OptionalLong を定義できるようになりました。
- プロパティに java.util.OptionalDouble を定義できるようになりました。
Daoインタフェース¶
- パラメータや戻り値の型に java.util.Optional を定義できるようになりました。
- パラメータや戻り値の型に java.util.OptionalInt を定義できるようになりました。
- パラメータや戻り値の型に java.util.OptionalLong を定義できるようになりました。
- パラメータや戻り値の型に java.util.OptionalDouble を定義できるようになりました。
- java.util.stream.Stream を使って検索結果を処理できるようになりました。
- java.util.stream.Collector を使って検索結果を処理できるようになりました。
- @Delegate が廃止になり、代わりにデフォルトメソッドが使えるようになりました。
- IterationCallback を使った検索が禁止されました。 代わりに java.util.stream.Stream を使った検索をしてください。
FAQ¶
一般的な質問¶
Domaとはどのような意味ですか?¶
D omain O riented Database MA pping Framework の略です。
注釈処理とはなんですか?¶
Java 6 で導入された Pluggable Annotation Processing API を利用した処理で、 コンパイル時のソースコード検証や、ソースコード生成が可能です。
Domaでは、注釈処理を使用して以下のことを実現しています。
- エンティティクラスやドメインクラスのメタ情報の生成
- Daoのインタフェースから実装クラスの生成
- SQLファイルのバリデーション
動作環境に関する質問¶
どのバージョンのJREをサポートしていますか?¶
JER 8 以上をサポートしています。
Domaを動作させるのに必要なライブラリは何ですか?¶
何もありません。 DomaはJRE以外のどんなライブラリにも依存していません。
開発環境に関する質問¶
どのバージョンのJDKをサポートしていますか?¶
JDK 8 以上をサポートしています。
推奨されたIDE(統合開発環境)はありますか?¶
Eclipse です。
DomaのjarをEclipseのビルドパスに設定しましたが注釈処理が実行されません。¶
Factory PathにもDomaのjarファイルを登録してください。
Factory Pathの設定画面は、プロジェクトのプロパティ設定画面から、 Java - Compiler - Annotation Processing - Factory Path と辿れます。 Annotation ProcessingとFactory Pathの画面では、 「Enable project specific settings」のチェックボックスをチェックしてください。
注釈処理で生成されたコードはどこに出力されますか?¶
Eclipseを利用している場合、 デフォルトではプロジェクト直下の .apt_generated ディレクトリに出力されます。
Eclipseを使用していますが、.apt_generated ディレクトリがみつかりません。¶
単にPackage Explorerビュー上に .apt_generated ディレクトリが表示されていないだけかもしれません。 .apt_generated は、名称が . で始まっているために、 Package Explorerビューで表示対象外になります。 対応方法としては以下のいずれかを選択してください。
- Nivigatorビューから .apt_generated ディレクトリを確認する
- Package Explorerビューのフィルタリングの設定を変更し .apt_generated ディレクトリを表示させる
- 出力先ディレクトリを変更し名称が . で始まらないようにする
SQLファイルが見つかりません。¶
SQLファイルが存在するにも関わらず、次のエラーメッセージが出力されることがあります。
[DOMA4019] SQLファイル[META-INF/../select.sql]がクラスパスから見つかりませんでした
SQLファイルはクラスの出力ディレクトリから検索されます。 SQLファイルの出力ディレクトリとクラスの出力ディレクトリが同じであることを確認してください。
Eclipseの場合、プロジェクトのプロパティの「Java Build Path」の設定画面で、 ソースフォルダごとに出力先ディレクトリを変更可能になっています。
DBアクセスライブラリとしての機能に関する質問¶
SQLを自動生成する機能はありますか?¶
はい。
更新系SQL、ストアドプロシージャー/ファンクション呼び出しについてはSQLを自動で生成できます。 検索系のSQLについては、自動生成機能はありませんが、ファイルに外部化したSQLを実行し、 その結果をJavaのオブジェクトにマッピングする機能があります。
詳しくは クエリ を参照してください。
条件が動的に変わるSQLはどのように実行できますか?¶
SQLファイルに、SQLコメントを使って条件を指定できます。 SQLコメントは実行時に解析され、条件によって異なるSQLが生成されます。
詳しくは SQL を参照してください。
1対1 や 1対n などデータベース上のリレーションシップをJavaオブジェクトにマッピングできますか?¶
いいえ、できません。
Domaでは、SQLの結果セットの1行をそのまま1つのエンティティのインスタンスにマッピングします。 このほうがシンプルでわかりやすいと考えているためです。
コネクションプーリングの機能はありますか?¶
いいえ、Domaでは提供していません。
コネクションプーリング機能をもつアプリケーションサーバー、フレームワーク、 ライブラリ等と組み合わせて使用してください。