Apache Cayenne の ORM (Object-Relational Mapping) 機能を使った MySQL データベースとの対話

この記事では、**Apache Cayenne**のオブジェクトリレーショナルマッピング(ORM)機能を使用して、Mavenを使用して構築した小さな**Java**プロジェクトから**MySQL**データベースを操作します。

*本ブログは英語版からの翻訳です。オリジナルは[こちら](https://www.alibabacloud.com/blog/a-quick-start-into-apache-cayenne_595405)からご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。*

#プロジェクトの設定
MavenはJavaアプリケーションを構築するために最もよく使われているツールの一つです。このセクションでは、プロジェクトの依存関係を設定します（これはあなたのシステムが空であることを前提としています）。

最初のステップとして、Apache CayenneとMYSQLコネクタ（具体的にはJDBCドライバ）を追加するために、以下の依存関係を追加してみましょう。

```
<dependency>
   <groupId>org.apache.cayenne</groupId>
   <artifactId>cayenne-server</artifactId>
   <version>4.0.1</version>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.14</version>
   <scope>runtime</scope>
</dependency>
```

モデラープラグインは `pom.xml` にも定義されている必要があります。これは、現在のプロジェクトのマッピングファイルを開いたときに、コマンドラインからApache Cayenne Modelerプラグインを起動するために使われるmavenコマンドです。

```
<plugin>
   <groupId>org.apache.cayenne.plugins</groupId>
   <artifactId>cayenne-modeler-maven-plugin</artifactId>
   <version>4.0.1</version>

   <configuration>
      <modelFile>${project.basedir}/src/main/resources/cayenne-blog.xml</modelFile>
   </configuration>
</plugin>
```

一般的にモデラーは、データベースと実際のJavaモデルクラスとの間のマッピングを設計し、設定するための推奨される方法です。

[このページ](http://cayenne.apache.org/download.html?spm=a2c65.11461447.0.0.640c764cCNwoDP)からダウンロードできます。あなたの特定のOS用に作られたバージョンをダウンロードする必要があるでしょうし、代わりに、Mavenプラグインとして含まれているクロスプラットフォームバージョン(JAR)を使用することもできます。この記事を書いている時点での最新の安定版はバージョン[4.1](https://github.com/apache/cayenne/blob/STABLE-4.1/RELEASE-NOTES.txt?spm=a2c65.11461447.0.0.640c764cCNwoDP&file=RELEASE-NOTES.txt)です。このバージョンはJava 1.8以降が必要です。

次のステップとして、`mvn clean install`コマンドでプロジェクトをビルドし、`mvn cayenne-modeler:run`コマンドでモデラーGUIを起動して、この画面を出力してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/0552daa4-bdd7-6357-222a-403fa350f6df.png)



MySQL 以外のものを使用している場合は、JDBC ドライバも変更する必要があるため、設定はデータベースに依存します。ここでは、対応するドライバと一緒に完全なリストを示します。

#マッピングとデータベースの設計
例えば、2つのテーブル間の1対多の関係を示すcayenne_blogという既存のデータベースがあり、その中で以下のようなパラメータが定義されているとします。

- author(著者): id (PK) と name
- article（記事）：id (PK)、タイトル、内容、author_id (FK)
では、この例のDBを参照したSQLコマンドを考えてみましょう。


```
CREATE TABLE `author` (
  `id` int(11) NOT NULL,
  `name` varchar(250) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Indexes for table `article`
--
ALTER TABLE `article`
  ADD PRIMARY KEY (`id`),
  ADD KEY `author_id` (`author_id`);

--
-- Indexes for table `author`
--
ALTER TABLE `author`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT for table `article`
--
ALTER TABLE `article`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT for table `author`
--
ALTER TABLE `author`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- Constraints for table `article`
--
ALTER TABLE `article`
  ADD CONSTRAINT `article_ibfk_1` FOREIGN KEY (`author_id`) REFERENCES `author` (`id`);
```

`db.sql](https://github.com/dassiorleando/apache-cayenne/blob/master/db.sql)`ファイルを`phpMyAdmin`にインポートするか、ターミナルのMYSQLサーバーから以下のコマンドを実行します: `mysql < db.sql.`

では、モデラープラグインの設定である`cayenne-maven-plugin`というプラグインを`pom.xml`に追加してみましょう。

```
<plugin>
   <groupId>org.apache.cayenne.plugins</groupId>
   <artifactId>cayenne-maven-plugin</artifactId>
   <version>4.0.1</version>

   <configuration>
      <map>${project.basedir}/src/main/resources/blog.map.xml</map>
      <dataSource>
         <driver>com.mysql.jdbc.Driver</driver>
         <url>jdbc:mysql://localhost:3306/cayenne_blog</url>
         <username>root</username>
         <password>root</password>
      </dataSource>
      <dbImport>
         <defaultPackage>com.dassiorleando.apachecayenne.models</defaultPackage>
      </dbImport>
   </configuration>

   <dependencies>
      <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>5.1.44</version>
         <scope>runtime</scope>
      </dependency>
   </dependencies>
</plugin>
```

ここではORMがデータを保存する場所(`<data source>`)とマッピングファイルを保存する場所(`<map>`)を指定します。この設定から、データベース名は`cayenne_blog`、ユーザデータベースの認証情報は`root:root`(MYSQLサーバのものに合わせて更新してください)、デフォルトパッケージはプロジェクト構造体のもの(モデルクラスを作成するパッケージ)を使用していることがわかります。

最後に、プロジェクトのコマンドラインからcbimportコマンドを使用します: `mvn cayenne:cdbimport`。`cdbimport`はXMLマップファイルを既存のデータベースと同期させるので、以下のようなログが得られるはずです。

```
INFO] +++ Connecting: SUCCESS.
[INFO] Detected and installed adapter: org.apache.cayenne.dba.mysql.MySQLAdapter
[INFO]   Table: cayenne_blog.AUTO_PK_SUPPORT
[INFO]   Table: cayenne_blog.article
[INFO]   Table: cayenne_blog.author
[INFO]     Db Relationship : toOne  (article.author_id, author.id)
[INFO]     Db Relationship : toMany (author.id, article.author_id)
[INFO] 
[INFO] Map file does not exist. Loaded db model will be saved into '/Users/dassiorleando/projects/opensource/apache-cayenne/src/main/resources/blog.map.xml'
[INFO] 
[INFO] Detected changes: 
[INFO]     Create Table         article
[INFO]     Create Table         author
[INFO]     Create Table         AUTO_PK_SUPPORT
[INFO] 
[WARNING] Can't find ObjEntity for author
[WARNING] Db Relationship (Db Relationship : toOne  (article.author_id, author.id)) will have GUESSED Obj Relationship reflection. 
[INFO] Migration Complete Successfully.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.165 s
[INFO] Finished at: 2019-07-22T17:40:36+01:00
[INFO] Final Memory: 10M/164M
[INFO] ------------------------------------------------------------------------
```

Javaクラスを生成してみましょう: `mvn cayenne:cgen`


```
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Building apache-cayenne 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- cayenne-maven-plugin:4.0.1:cgen (default-cli) @ apache-cayenne ---
[INFO] Generating superclass file: /Users/dassiorleando/projects/opensource/apache-cayenne/src/main/java/com/dassiorleando/apachecayenne/model/auto/_Article.java
[INFO] Generating class file: /Users/dassiorleando/projects/opensource/apache-cayenne/src/main/java/com/dassiorleando/apachecayenne/model/Article.java
[INFO] Generating superclass file: /Users/dassiorleando/projects/opensource/apache-cayenne/src/main/java/com/dassiorleando/apachecayenne/model/auto/_Author.java
[INFO] Generating class file: /Users/dassiorleando/projects/opensource/apache-cayenne/src/main/java/com/dassiorleando/apachecayenne/model/Author.java
[INFO] Generating superclass file: /Users/dassiorleando/projects/opensource/apache-cayenne/src/main/java/com/dassiorleando/apachecayenne/model/auto/_AutoPkSupport.java
[INFO] Generating class file: /Users/dassiorleando/projects/opensource/apache-cayenne/src/main/java/com/dassiorleando/apachecayenne/model/AutoPkSupport.java
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

```

これで、プロジェクトの構造が変更されているのがすぐにわかるはずです。ここでは、`_Article.java`と`_Author.java`の話をしています（どちらも`CayenneDataObject`を拡張したファイルです）。同じ設定（XML形式）が`resources/blog.map.xml`ファイルに入れられていると見ることができます。

次に、`mvn cayenne-modeler:run`というコマンドを入力してモデラーを起動します。New Projectをクリックし、次のページでマッピングファイルのData Domain Nam (blog)を指定します。ここでは、作成したマップファイルと同じフォルダに保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/7d4c9144-333d-90f8-6c15-604883d60ff9.png)

次に、File > Import Datamapをクリックして、データマップへのリンクを許可するUIにアクセスします。

`cayenne-blog.xml`と`blog.map.xml`がリンクされたら、モデラーからモデルを更新してクラスに反映させることができます。以下のようになります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/1bb2f84d-eb5d-5bcb-18fd-ba9d601db0fd.png)

Apache Cayenne は 3 種類の主要な鍵戦略をサポートしています。

- Cayenne-Generated: PK生成を管理します。
- データベース生成: PK はデータベースエンジンによって管理されます。
- カスタムシーケンス：ここにカスタムロジックを実装する必要があります。
次のスクリーンショットでは、著者テーブルには、データベースで管理されているオートインクリメントされた整数であるidが設定されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/3a8fa3c4-a4c1-e656-309b-fe1e48586abd.png)

注: 次の図に示すように、当社のデータベース構成と正確に一致するDataNodeを作成してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/73e31905-cc07-f3dd-0c85-afa7146f33b7.png)

Mavenプロジェクトの`resources`ディレクトリに、以下の内容の`cayenne-blog`という特別なXMLファイルを作成します。

```
<?xml version="1.0" encoding="utf-8"?>
<domain project-version="9">
   <map name="blog"/>

   <node name="datanode"
       factory="org.apache.cayenne.configuration.server.XMLPoolingDataSourceFactory"
       schema-update-strategy="org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy">
      <map-ref name="blog"/>
      <data-source>
         <driver value="com.mysql.jdbc.Driver"/>
         <url value="jdbc:mysql://localhost:3306/cayenne_blog"/>
         <connectionPool min="1" max="1"/>
         <login userName="root" password="root"/>
      </data-source>
   </node>
</domain>
```

`XMLPoolingDataSourceFactory`は、`DataNodeDescriptor`に関連付けられたXMLリソースからJDBC接続情報をロードする役割を担う。

#マッピング構造
Apache Cayenne はモデルを記述するための独自の構文を持っています。

- `DataNode(<node>)`: データベースのモデル。データベース名、ドライバ、データベースユーザの認証情報など、データベースに接続するために必要なすべての情報が含まれています。
- `DataMap(<data-map>)`:関係を持つ永続的なエンティティのコンテナです。
- `DbAttribute(<db-attribute>)`: データベーステーブルのカラムを表します。
- `DbEntity(<db-entity>)`: 単一のデータベーステーブルまたはビューのモデル。DbAttributesとリレーションシップを持つことができます。

- `ObjEntity(<obj-entity>)`: 単一の永続的なJavaクラスのモデルで、エンティティクラスのプロパティに対応するObjAttributesと、別のエンティティの型を持つプロパティであるObjRelationshipsで構成されています。
- `Embeddable(<embeddable>)`: ObjEntityのプロパティとして機能するJavaクラスのモデルですが、データベースの複数の列に対応しています。
- `procedure(<procedure>)`: データベースにストアドプロシージャを登録します。マッピングする際に使用します。

詳細は以下の[ガイド](https://cayenne.apache.org/docs/3.1/cayenne-guide/#cayenne-mapping-structure?spm=a2c65.11461447.0.0.640c764cCNwoDP)を参照してください。

#モデルへのCRUDの適用
このセクションでは、モデル(`Article`と`Author`)にいくつかの基本的な操作を適用します。`ObjectSelect`クラスはデータベースへの問い合わせに便利ないくつかの静的メソッドを持っていますが、挿入と更新のために、変更をコミットするために使用される`server's context(ObjectContext) `を使用する必要があります。

ここでは、私たちのプロジェクトに関連するサーバーのコンテキストを取得する方法を説明します。

```
ServerRuntime cayenneRuntime = ServerRuntime.builder()
        .addConfig("cayenne-blog.xml")
        .build();

ObjectContext context = cayenneRuntime.newContext();

```

注: cayenne-blog.xmlファイルはプロジェクトのリソースフォルダにあります。

###オブジェクトの作成
以下のクエリでオブジェクトを作成することができます。


```
/**
 * Save an author
 * @param name
 */
public void save(String name) {
    // Save a single author
    Author author = this.context.newObject(Author.class);
    author.setName(name);

    context.commitChanges();
}
```

###オブジェクトの読み取り
以下のクエリでオブジェクトを読み込むことができます。

```
/**
 * Find an author by its ID
 * @param id    the author's ID
 * @return      the matched author or null if not existing
 */
public Author findById(int id) {
    Author author = Cayenne.objectForPK(context, Author.class, id);
    return author;
}

/**
 * Looking for an author by name
 * @param name  the name to look up with
 * @return      the first matched author or null if not existing
 */
public Author findByName(String name) {
    Author foundAuthor = ObjectSelect.query(Author.class)
            .where(Author.NAME.eq(name))
            .selectOne(this.context);

    return foundAuthor;
}

/**
 * Find authors by name starting with(like%)
 * @param partName expected name part
 * @return         list of authors
 */
public List<Author> findByNameLike(String partName) {
    // Let's apply a case-insensitive LIKE on the Author's name column
    // We get all the authors with their name starting with "partName"
    List<Author> authorsLike = ObjectSelect.query(Author.class)
            .where(Author.NAME.likeIgnoreCase(partName + "%"))
            .select(context);

    return authorsLike;
}

/**
 * Find authors by name ending with
 * @param partName expected name part
 * @return         list of authors
 */
public List<Author> findByNameEndWith(String partName) {
    // All authors with names ending with "partName"
    List<Author> authorsEnd = ObjectSelect.query(Author.class)
            .where(Author.NAME.endsWith(partName))
            .select(context);

    return authorsEnd;
}
```

###クラスのすべてのレコードを検索する
これまでに保存されたすべての著者に以下のクエリを使用してクエリを実行することができます。

```
public List<Author> findAll() {
    // Looking for all authors
    List<Author> authors = ObjectSelect
            .query(Author.class)
            .select(this.context);
    return authors;
}
```

###オブジェクトの更新
以下のクエリでオブジェクトを更新することができます。

```
/**
 * Update an author
 * @param id        the author's ID
 * @param newName   the new name to set
 * @return          true for a successful operation and false unknown author
 */
public boolean update(int id, String newName) {
    if (StringUtils.isEmpty(newName)) return false;

    // Get the author to update
    Author author = this.findById(id);

    if (author == null) return false;

    // Set its name
    author.setName(newName);
    context.commitChanges();
    return true;
}
```

###オブジェクトへの関係付け
ここでは、著者が書いた記事と著者をリンクさせる方法を紹介します。

```
/**
 * Attach a fake article to the author
 * @param id    the author's ID
 * @return      true for a successful operation and false unknown author
 */
public boolean attachArticle(int id) {
    // Get the author to link with
    Author author = this.findById(id);

    if (author == null) return false;

    // Create a fake article and link it to the current author
    Article article = context.newObject(Article.class);
    article.setTitle("My post title");
    article.setContent("The content");
    article.setAuthor(author);

    context.commitChanges();

    // Get author's linked data (articles)
    List<Article> articles = author.getArticles();

    return true;
}

```

###オブジェクトの削除
以下のクエリでオブジェクトを削除することができます。

```
/**
 * Delete an author
 * @param id author's ID
 * @return   true for a successful operation and false unknown author
 */
public boolean delete(int id) {
    // Get the author to delete
    Author author = this.findById(id);

    if (author != null) {
        context.deleteObjects(author);
        context.commitChanges();
        return true;
    } else {
        return false;
    }
}
```

###クラスのすべてのレコードを削除する
Apache CayenneのAPIでは`SQLTemplate`を使ってテーブルの全レコードを削除することができますが、ここでは基本的なSQL削除クエリをターゲットクラスと一緒に提供するだけです。

```
// SQL delete queries for Author and Article classes
SQLTemplate deleteArticles = new SQLTemplate(Article.class, "delete from article");
SQLTemplate deleteAuthors = new SQLTemplate(Author.class, "delete from author");

// Applying the deletion queries
context.performGenericQuery(deleteArticles);
context.performGenericQuery(deleteAuthors);
```

###ExpressionとExpressionFactory
Apache Cayenne を使って高度なクエリを構築するには多くの可能性があります。しかし、ほとんどの場合は[Expression](https://cayenne.apache.org/docs/4.0/api/org/apache/cayenne/exp/Expression.html?spm=a2c65.11461447.0.0.640c764cCNwoDP)と[ExpressionFactory](https://cayenne.apache.org/docs/4.0/api/org/apache/cayenne/exp/ExpressionFactory.html?spm=a2c65.11461447.0.0.640c764cCNwoDP)クラスを使っていますが、ここではその例をいくつか紹介します。

- `likeExp`: `LIKE` 式を構築するために使用します。
- `likeIgnoreCaseExp`: `LIKE_IGNORE_CASE` 式を構築するために使用します。
- `containsExp`: 文字列内の任意の場所にマッチするパターンを持つ LIKE クエリに使用される式。
- `containsIgnoreCaseExp`: `containsExp`に似ていますが、大文字小文字を区別しないアプローチを使用します。
- `startsWithExp`: パターンは文字列の先頭にマッチします。
- `startsWithIgnoreCaseExp`: `startsWithExp` に似ていますが、大文字小文字を区別しないアプローチを使用します。
- `endsWithExp`: 文字列の末尾にマッチする式。
- `endsWithIgnoreCaseExp`: 大文字小文字を区別しないアプローチを使用する文字列の最後にマッチする式。
- `expTrue`: `True`の式に使用します。
- `expFalse`: ブール値の`False`の式に使用します。
- `andExp`: `and`演算子を使用して2つの式を連結する際に使用します。
- `orExp`: `or` 演算子を使用して 2 つの式を連結する場合に使用します。

#結論
このチュートリアルでは、Apache Cayenne のオブジェクトリレーショナルマッピング (ORM) 機能を MySQL データベース用に設定する方法と、基本的な CRUD 操作を一対多のリレーションシップを持つクエリの例として学習しました。この記事の完全なソースコードは[Github](https://github.com/dassiorleando/apache-cayenne?spm=a2c65.11461447.0.0.640c764cCNwoDP)にあります。

*アリババクラウドは日本に２つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
[アリババクラウドジャパン公式ページ](https://www.alibabacloud.com/ja)*