ノート:
- このチュートリアルは、Oracle提供の無料ラボ環境で入手できます。
- Oracle Cloud Infrastructureの資格証明、テナンシおよびコンパートメントに例の値を使用します。演習を完了するときは、これらの値をクラウド環境に固有の値に置き換えます。
TASK 4:データベース問合せの実装とMicronautアプリケーションの作成
この演習では、データベース問合せを実装し、Oracle Autonomous Databaseに接続するMicronautアプリケーションをローカルに構築します。
見積時間: 30分
タスク・コンテンツ
このタスクでは、次のことを行います。
- Oracle Database表にマップするMicronautデータ・エンティティの作成
- クエリーを実装するためのMicronautデータ・リポジトリの定義
- MicronautコントローラをRESTエンドポイントとして公開
- アプリケーションの起動時にデータを移入
- Micronautアプリケーションの統合テストの実行
ステップ1: Oracle Database表にマップするMicronautデータ・エンティティの作成
前のタスクで、OWNER
という表を作成し、PET
という表を実行するSQLスクリプトを追加しました。次に、データベース表からデータを読み取るために使用できるエンティティ・クラスを定義する必要があります。
-
src/main/java/com/example
パッケージのOwner
を表すエンティティ・クラスを作成します。src/main/java/com/example
を右クリックしてコンテンツ・メニューを展開し、「新規ファイル」を選択してOwner.java
という名前を付け、次のコードを貼り付けます。package com.example; import io.micronaut.core.annotation.Creator; import io.micronaut.data.annotation.GeneratedValue; import io.micronaut.data.annotation.Id; import io.micronaut.data.annotation.MappedEntity; @MappedEntity public class Owner { // The ID of the class uses a generated sequence value @Id @GeneratedValue private Long id; private final String name; private int age; // the constructor reads column values by the name of each constructor argument @Creator public Owner(String name) { this.name = name; } // each property of the class maps to a database column public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } }
@MappedEntity
注釈は、エンティティがデータベース表にマップされていることを示すために使用されます。デフォルトでは、これはクラスと同じ名前(この場合はowner
)を使用する表になります。表の列は、各Javaプロパティによって表されます。前述の例では、主キーを表すために
id
列が使用され、Autonomous Databaseでidentity
列の使用を想定するように@GeneratedValue
によってマッピングが設定されます。@Creator
注釈は、マップされたエンティティのインスタンス化に使用されるコンストラクタで使用され、必要な列の表現にも使用されます。この場合、name
列が必要であり、age
列は不要であり、setAge
セッターを使用して独立して設定できます。 -
Pet
エンティティを表すPet.java
ファイルを作成して、src/main/java/com/example
の下にpet
表をモデル化します。package com.example; import io.micronaut.core.annotation.Creator; import io.micronaut.core.annotation.Nullable; import io.micronaut.data.annotation.AutoPopulated; import io.micronaut.data.annotation.Id; import io.micronaut.data.annotation.MappedEntity; import io.micronaut.data.annotation.Relation; import java.util.UUID; @MappedEntity public class Pet { // This class uses an auto populated UUID for the primary key @Id @AutoPopulated private UUID id; private final String name; // A relation is defined between Pet and Owner @Relation(Relation.Kind.MANY_TO_ONE) private final Owner owner; private PetType type = PetType.DOG; // The constructor defines the columns to be read @Creator public Pet(String name, @Nullable Owner owner) { this.name = name; this.owner = owner; } public Owner getOwner() { return owner; } public String getName() { return name; } public UUID getId() { return id; } public void setId(UUID id) { this.id = id; } public PetType getType() { return type; } public void setType(PetType type) { this.type = type; } public enum PetType { DOG, CAT } }
Pet
クラスは、自動的に移入されたUUID
を主キーとして使用して、ID生成に対する様々なアプローチを示すことに注意してください。Pet
クラスとOwner
クラスの関係も、@Relation(Relation.Kind.MANY_TO_ONE)
注釈を使用して定義されます。これは多対1の関係であることを示します。そのためには、問合せを実装するためにリポジトリ・インタフェースの定義に移行します。
ステップ2:クエリーを実装するためのMicronautデータ・リポジトリの定義
Micronaut Dataは、コンパイル時にデータ・リポジトリ・パターンを使用してSQL問合せを自動的に実装するインタフェースを定義する概念をサポートしています。この項では、このMicronautデータ機能を利用します。
-
src/main/java/com/example
の下にrepositories
という別のフォルダを作成します。 -
CrudRepository
から拡張され、OwnerRepository.java
というファイルのORACLE
ダイアレクトを使用して@JdbcRepository
で注釈が付けられた新しいリポジトリ・インタフェースを定義します。package com.example.repositories; import com.example.Owner; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.query.builder.sql.Dialect; import io.micronaut.data.repository.CrudRepository; import java.util.List; import java.util.Optional; // The @JdbcRepository annotation indicates the database dialect @JdbcRepository(dialect = Dialect.ORACLE) public interface OwnerRepository extends CrudRepository<Owner, Long> { @Override List<Owner> findAll(); // This method will compute at compilation time a query such as // SELECT ID, NAME, AGE FROM OWNER WHERE NAME = ? Optional<Owner> findByName(String name); }
CrudRepository
インタフェースには、2つの一般的な引数タイプがあります。1つ目はエンティティのタイプ(この場合はOwner
)で、2つ目はIDのタイプ(この場合はLong
)です。CrudRepository
インタフェースは、コンパイル時に計算された適切なSQL挿入、選択、更新および削除を使用して、データベースからエンティティを作成、読取り、更新および削除できるメソッドを定義します。詳細は、Javadoc (CrudRepository)を参照してください。JDBC問合せを実行するインタフェース内でメソッドを定義し、正しいトランザクション・セマンティクス(問合せの読取り専用トランザクション)の定義、問合せの実行および結果セットの
Owner
エンティティ・クラスへのマッピングなど、すべての複雑な詳細を自動的に処理できます。前述の
findByName
メソッドは、コンパイル時にSELECT ID, NAME, AGE FROM OWNER WHERE NAME = ?
などの問合せを自動的に生成します。問合せ方法および定義できる問合せのタイプの詳細は、Micronautデータ・ドキュメントの問合せ方法のドキュメントを参照してください。
-
OwnerRepository
を配置して、別のリポジトリと今回は、データ転送オブジェクト(DTO)を使用して最適化された問合せを実行します。したがって、最初に、src/main/java/com/example/repositories
の下のNameDTO.java
というファイルにDTOクラスを作成する必要があります。package com.example.repositories; import io.micronaut.core.annotation.Introspected; @Introspected public class NameDTO { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
DTOは単純なPOJOで、特定の問合せに必要な列のみを選択できるため、より最適化された問合せが生成されます。
-
同じ場所
src/main/java/com/example/repositories
でDTOを使用するPet
エンティティのPetRepository.java
というファイルで、PetRepository
というリポジトリを定義します。package com.example.repositories; import com.example.Pet; import io.micronaut.data.annotation.Join; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.query.builder.sql.Dialect; import io.micronaut.data.repository.PageableRepository; import java.util.List; import java.util.Optional; import java.util.UUID; @JdbcRepository(dialect = Dialect.ORACLE) public interface PetRepository extends PageableRepository<Pet, UUID> { List<NameDTO> list(); @Join("owner") Optional<Pet> findByName(String name); }
DTOを返す
list
メソッドを書き留めます。このメソッドは、コンパイル時に再度実装されますが、今回は、Pet
表のすべての列を取得するのではなく、name
列および定義できるその他の列のみを取得します。@Join
注釈は、結合されたオブジェクト(Owner
)を問い合せてインスタンス化し、問合せ対象のPet
のOwner
フィールドに割り当てます。findByName
メソッドは、@Join
注釈であるMicronautデータの別の重要な機能を使用するため、興味もあります。これにより、結合パスを指定できるため、データベース結合を介して必要なデータを正確に取得できるため、問合せがより効率的になります。
データ・リポジトリを所定の位置に置いて、RESTエンドポイントを公開します。
ステップ3: MicronautコントローラをRESTエンドポイントとして公開
MicronautのRESTエンドポイントは簡単に記述でき、コントローラ(MVCパターンに従って)として定義されます。
-
src/main/java/com/example/
の下にフォルダcontrollers
を作成します。 -
OwnerController.java
というファイルに新しいOwnerController
クラスを定義します。package com.example.controllers; import java.util.List; import java.util.Optional; import javax.validation.constraints.NotBlank; import com.example.Owner; import com.example.repositories.OwnerRepository; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.micronaut.scheduling.TaskExecutors; import io.micronaut.scheduling.annotation.ExecuteOn; @Controller("/owners") @ExecuteOn(TaskExecutors.IO) class OwnerController { private final OwnerRepository ownerRepository; OwnerController(OwnerRepository ownerRepository) { this.ownerRepository = ownerRepository; } @Get("/") List<Owner> all() { return ownerRepository.findAll(); } @Get("/{name}") Optional<Owner> byName(@NotBlank String name) { return ownerRepository.findByName(name); } }
コントローラ・クラスは、コントローラがマップするルートURIを定義するために使用できる
@Controller
注釈で定義されます(この場合は/owners
)。@ExecuteOn
注釈は、コントローラがデータベースとのI/O通信を実行するため、I/Oスレッド・プールで実行する必要があることをMicronautに伝えるために使用されます。OwnerController
クラスは、Micronaut依存性インジェクションを使用して、前に定義したOwnerRepository
リポジトリ・インタフェースへの参照を取得し、2つのエンドポイントを実装するために使用されます。/
-ルート・エンドポイントにはすべての所有者がリストされます/{name}
- 2番目のエンドポイントでは、URIテンプレートを使用して所有者を名前で検索できます。URI変数{name}
の値は、byName
メソッドへのパラメータとして提供されます。
-
次に、
src/main/java/com/example/controllers
の下のPetController.java
というファイルに、PetController
という別のRESTエンドポイントを定義します。package com.example.controllers; import java.util.List; import java.util.Optional; import com.example.repositories.NameDTO; import com.example.Pet; import com.example.repositories.PetRepository; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.micronaut.scheduling.TaskExecutors; import io.micronaut.scheduling.annotation.ExecuteOn; @ExecuteOn(TaskExecutors.IO) @Controller("/pets") class PetController { private final PetRepository petRepository; PetController(PetRepository petRepository) { this.petRepository = petRepository; } @Get("/") List<NameDTO> all() { return petRepository.list(); } @Get("/{name}") Optional<Pet> byName(String name) { return petRepository.findByName(name); } }
今回は、
PetRepository
が注入されて、ペットとペットのリストが名前で公開されます。
ステップ4:アプリケーションの起動時のデータの移入
次のステップは、起動時にアプリケーション・データを移入することです。これを行うには、Micronautアプリケーション・イベントを使用します。
src/main/java/com/example/Application.java
クラスを開き、初期ファイル・コンテンツを次のように置き換えます。
package com.example;
import com.example.repositories.OwnerRepository;
import com.example.repositories.PetRepository;
import io.micronaut.context.event.StartupEvent;
import io.micronaut.runtime.Micronaut;
import io.micronaut.runtime.event.annotation.EventListener;
import jakarta.inject.Singleton;
import javax.transaction.Transactional;
import java.util.Arrays;
@Singleton
public class Application {
private final OwnerRepository ownerRepository;
private final PetRepository petRepository;
Application(OwnerRepository ownerRepository, PetRepository petRepository) {
this.ownerRepository = ownerRepository;
this.petRepository = petRepository;
}
public static void main(String[] args) {
Micronaut.run(Application.class);
}
@EventListener
@Transactional
void init(StartupEvent event) {
// clear out any existing data
petRepository.deleteAll();
ownerRepository.deleteAll();
// create data
Owner fred = new Owner("Fred");
fred.setAge(45);
Owner barney = new Owner("Barney");
barney.setAge(40);
ownerRepository.saveAll(Arrays.asList(fred, barney));
Pet dino = new Pet("Dino", fred);
Pet bp = new Pet("Baby Puss", fred);
bp.setType(Pet.PetType.CAT);
Pet hoppy = new Pet("Hoppy", barney);
petRepository.saveAll(Arrays.asList(dino, bp, hoppy));
}
}
データを保持できるように、コンストラクタは依存してリポジトリ定義を注入するように変更されています。
最後に、init
メソッドには、StartupEvent
を受け取る引数とともに@EventListener
注釈が付けられます。このイベントは、アプリケーションが起動して実行されるとコールされ、アプリケーションの準備が完了したときにデータを永続化するために使用できます。
残りの例は、CrudRepositoryインタフェースのsaveAllメソッドを使用して少数のエンティティを保存する方法を示しています。
メソッドの実行中に例外が発生した場合にロールバックされるJDBCトランザクションで、Micronautデータがinit
メソッドの実行をラップすることを確認するメソッド上で、javax.transaction.Transactional
を宣言します。
ステップ5: Micronautアプリケーションの統合テストの実行
アプリケーションは、アプリケーションが正常に起動できるかどうかをチェックする単一のテストですでに設定されています(そのため、前の項で定義されたinit
メソッドのロジックをテストします)。
-
上部のナビゲーションから、「ターミナル」、「新規ターミナル」の順に移動します。
-
test
目標を実行してテストを実行します。./mvnw test
または、Gradleを使用している場合は、
test
タスクを実行します:./gradlew test
テスト実行の最後にBUILD SUCCESSメッセージが表示されます。
次のタスクに進むことができます。